function-creator
Create Convex queries, mutations, and actions with proper validation, authentication, and error handling. Use when implementing new API endpoints.
- License 许可
- AGPL-3.0
- 所属仓库
- baisharg/ASTN
- 最近更新
- 2026-11-13
- Stars
- ★ 2
- Forks
- 2
01 前言 Front Matter
- name
- function-creator
- description
- Create Convex queries, mutations, and actions with proper validation, authentication, and error handling. Use when implementing new API endpoints.
03 SKILL.md 正文
Convex Function Creator
Generate secure, type-safe Convex functions following all best practices.
When to Use
- Creating new query functions (read data)
- Creating new mutation functions (write data)
- Creating new action functions (external APIs, long-running)
- Adding API endpoints to your Convex backend
Function Types
Queries (Read-Only)
- Can only read from database
- Cannot modify data or call external APIs
- Cached and reactive
- Run in transactions
import { query } from './_generated/server'
import { v } from 'convex/values'
export const getTask = query({
args: { taskId: v.id('tasks') },
returns: v.union(
v.object({
_id: v.id('tasks'),
text: v.string(),
completed: v.boolean(),
}),
v.null(),
),
handler: async (ctx, args) => {
return await ctx.db.get(args.taskId)
},
})
Mutations (Transactional Writes)
- Can read and write to database
- Cannot call external APIs
- Run in ACID transactions
- Automatic retries on conflicts
import { mutation } from './_generated/server'
import { v } from 'convex/values'
export const createTask = mutation({
args: {
text: v.string(),
priority: v.optional(
v.union(v.literal('low'), v.literal('medium'), v.literal('high')),
),
},
returns: v.id('tasks'),
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity()
if (!identity) throw new Error('Not authenticated')
return await ctx.db.insert('tasks', {
text: args.text,
priority: args.priority ?? 'medium',
completed: false,
createdAt: Date.now(),
})
},
})
Actions (External + Non-Transactional)
- Can call external APIs (fetch, AI, etc.)
- Can call mutations via
ctx.runMutation - Cannot directly access database
- No automatic retries
- Use
"use node"directive when needing Node.js APIs
Important: If your action needs Node.js-specific APIs (crypto, third-party SDKs, etc.), add "use node" at the top of the file. Files with "use node" can ONLY contain actions, not queries or mutations.
'use node' // Required for Node.js APIs like OpenAI SDK
import { action } from './_generated/server'
import { api } from './_generated/api'
import { v } from 'convex/values'
import OpenAI from 'openai'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
export const generateTaskSuggestion = action({
args: { prompt: v.string() },
returns: v.string(),
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity()
if (!identity) throw new Error('Not authenticated')
// Call OpenAI (requires "use node")
const completion = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: args.prompt }],
})
const suggestion = completion.choices[0].message.content
// Write to database via mutation
await ctx.runMutation(api.tasks.createTask, {
text: suggestion,
})
return suggestion
},
})
Note: If you only need basic fetch (no Node.js APIs), you can omit "use node". But for third-party SDKs, crypto, or other Node.js features, you must use it.
Required Components
1. Argument Validation
Always define args with validators:
args: {
id: v.id("tasks"),
text: v.string(),
count: v.number(),
enabled: v.boolean(),
tags: v.array(v.string()),
metadata: v.optional(v.object({
key: v.string(),
})),
}
2. Return Type Validation
Always define returns:
returns: v.object({
_id: v.id('tasks'),
text: v.string(),
})
// Or for arrays
returns: v.array(
v.object({
/* ... */
}),
)
// Or for nullable
returns: v.union(
v.object({
/* ... */
}),
v.null(),
)
3. Authentication Check
Always verify auth in public functions:
const identity = await ctx.auth.getUserIdentity()
if (!identity) {
throw new Error('Not authenticated')
}
4. Authorization Check
Always verify ownership/permissions:
const task = await ctx.db.get(args.taskId)
if (!task) {
throw new Error('Task not found')
}
if (task.userId !== user._id) {
throw new Error('Unauthorized')
}
Complete Examples
Secure Query with Auth
export const getMyTasks = query({
args: {
status: v.optional(v.union(v.literal('active'), v.literal('completed'))),
},
returns: v.array(
v.object({
_id: v.id('tasks'),
text: v.string(),
completed: v.boolean(),
}),
),
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity()
if (!identity) throw new Error('Not authenticated')
const user = await ctx.db
.query('users')
.withIndex('by_token', (q) =>
q.eq('tokenIdentifier', identity.tokenIdentifier),
)
.unique()
if (!user) throw new Error('User not found')
let query = ctx.db
.query('tasks')
.withIndex('by_user', (q) => q.eq('userId', user._id))
const tasks = await query.collect()
if (args.status) {
return tasks.filter((t) =>
args.status === 'completed' ? t.completed : !t.completed,
)
}
return tasks
},
})
Secure Mutation with Validation
export const updateTask = mutation({
args: {
taskId: v.id('tasks'),
text: v.optional(v.string()),
completed: v.optional(v.boolean()),
},
returns: v.id('tasks'),
handler: async (ctx, args) => {
// 1. Authentication
const identity = await ctx.auth.getUserIdentity()
if (!identity) throw new Error('Not authenticated')
// 2. Get user
const user = await ctx.db
.query('users')
.withIndex('by_token', (q) =>
q.eq('tokenIdentifier', identity.tokenIdentifier),
)
.unique()
if (!user) throw new Error('User not found')
// 3. Get resource
const task = await ctx.db.get(args.taskId)
if (!task) throw new Error('Task not found')
// 4. Authorization
if (task.userId !== user._id) {
throw new Error('Unauthorized')
}
// 5. Update
const updates: Partial<typeof task> = {}
if (args.text !== undefined) updates.text = args.text
if (args.completed !== undefined) updates.completed = args.completed
await ctx.db.patch(args.taskId, updates)
return args.taskId
},
})
Action Calling External API
Create separate file for actions that need Node.js:
// convex/taskActions.ts
'use node' // Required for SendGrid SDK
import { action } from './_generated/server'
import { api } from './_generated/api'
import { v } from 'convex/values'
import sendgrid from '@sendgrid/mail'
sendgrid.setApiKey(process.env.SENDGRID_API_KEY)
export const sendTaskReminder = action({
args: { taskId: v.id('tasks') },
returns: v.boolean(),
handler: async (ctx, args) => {
// 1. Auth
const identity = await ctx.auth.getUserIdentity()
if (!identity) throw new Error('Not authenticated')
// 2. Get data via query
const task = await ctx.runQuery(api.tasks.getTask, {
taskId: args.taskId,
})
if (!task) throw new Error('Task not found')
// 3. Call external service (using Node.js SDK)
await sendgrid.send({
to: identity.email,
from: 'noreply@example.com',
subject: 'Task Reminder',
text: `Don't forget: ${task.text}`,
})
// 4. Update via mutation
await ctx.runMutation(api.tasks.markReminderSent, {
taskId: args.taskId,
})
return true
},
})
Note: Keep queries and mutations in convex/tasks.ts (without "use node"), and actions that need Node.js in convex/taskActions.ts (with "use node").
Internal Functions
For backend-only functions (called by scheduler, other functions):
import { internalMutation } from './_generated/server'
export const processExpiredTasks = internalMutation({
args: {},
handler: async (ctx) => {
// No auth needed - only callable from backend
const now = Date.now()
const expired = await ctx.db
.query('tasks')
.withIndex('by_due_date', (q) => q.lt('dueDate', now))
.collect()
for (const task of expired) {
await ctx.db.patch(task._id, { status: 'expired' })
}
},
})
Checklist
- [ ]
argsdefined with validators - [ ]
returnsdefined with validator - [ ] Authentication check (
ctx.auth.getUserIdentity()) - [ ] Authorization check (ownership/permissions)
- [ ] All promises awaited
- [ ] Indexed queries (no
.filter()on queries) - [ ] Error handling with descriptive messages
- [ ] Scheduled functions use
internal.*notapi.* - [ ] If using Node.js APIs:
"use node"at top of file - [ ] If file has
"use node": Only actions (no queries/mutations) - [ ] Actions in separate file from queries/mutations when using "use node"
04 同仓库更多 Skills
schema-builder
Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.
integration-tanstack-start
PostHog integration for TanStack Start full-stack applications
migration-helper
Plan and execute Convex schema migrations safely, including adding fields, creating tables, and data transformations. Use when schema changes affect existing data.
gsd:sprint
Autonomous phase execution with Codex validation. Runs plan/execute/verify cycles in a bash loop until done or halted.
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.
gsd:milestone-sprint
Run entire milestone autonomously with Codex validation. Auto-detects current milestone, executes all phases, and runs audit.