function-creator
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConvex Function Creator
Convex 函数创建工具
Generate secure, type-safe Convex functions following all best practices.
遵循最佳实践,生成安全、类型安全的Convex函数。
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
- 创建新的查询函数(读取数据)
- 创建新的变更函数(写入数据)
- 创建新的动作函数(调用外部API、长时间运行任务)
- 为Convex后端添加API端点
Function Types
函数类型
Queries (Read-Only)
查询函数(只读)
- Can only read from database
- Cannot modify data or call external APIs
- Cached and reactive
- Run in transactions
typescript
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);
},
});- 仅能从数据库读取数据
- 无法修改数据或调用外部API
- 支持缓存与响应式
- 在事务中执行
typescript
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
typescript
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(),
});
},
});- 可读写数据库
- 无法调用外部API
- 在ACID事务中执行
- 冲突时自动重试
typescript
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 directive when needing Node.js APIs
"use node"
Important: If your action needs Node.js-specific APIs (crypto, third-party SDKs, etc.), add at the top of the file. Files with can ONLY contain actions, not queries or mutations.
"use node""use node"typescript
"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 . But for third-party SDKs, crypto, or other Node.js features, you must use it.
"use node"- 可调用外部API(fetch、AI服务等)
- 可通过调用变更函数
ctx.runMutation - 无法直接访问数据库
- 无自动重试机制
- 需要Node.js API时,添加指令
"use node"
重要说明: 如果你的动作函数需要Node.js专属API(加密、第三方SDK等),请在文件顶部添加。带有的文件只能包含动作函数,不能包含查询或变更函数。
"use node""use node"typescript
"use node"; // 调用OpenAI SDK等Node.js API时必填
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");
// 调用OpenAI(需要"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;
// 通过变更函数写入数据库
await ctx.runMutation(api.tasks.createTask, {
text: suggestion,
});
return suggestion;
},
});注意: 如果仅需要基础fetch(无需Node.js API),可以省略。但对于第三方SDK、加密或其他Node.js特性,必须添加该指令。
"use node"Required Components
必备组件
1. Argument Validation
1. 参数验证
Always define with validators:
argstypescript
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(),
})),
}务必使用验证器定义:
argstypescript
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
2. 返回类型验证
Always define :
returnstypescript
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())务必定义:
returnstypescript
returns: v.object({
_id: v.id("tasks"),
text: v.string(),
})
// 数组类型示例
returns: v.array(v.object({ /* ... */ }))
// 可空类型示例
returns: v.union(v.object({ /* ... */ }), v.null())3. Authentication Check
3. 身份认证检查
Always verify auth in public functions:
typescript
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}务必在公开函数中验证身份:
typescript
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("未通过身份认证");
}4. Authorization Check
4. 权限校验
Always verify ownership/permissions:
typescript
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");
}务必验证所有权/权限:
typescript
const task = await ctx.db.get(args.taskId);
if (!task) {
throw new Error("任务不存在");
}
if (task.userId !== user._id) {
throw new Error("无操作权限");
}Complete Examples
完整示例
Secure Query with Auth
带身份认证的安全查询函数
typescript
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;
},
});typescript
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("未通过身份认证");
const user = await ctx.db
.query("users")
.withIndex("by_token", q =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
if (!user) throw new Error("用户不存在");
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
带验证的安全变更函数
typescript
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;
},
});typescript
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. 身份认证
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("未通过身份认证");
// 2. 获取用户信息
const user = await ctx.db
.query("users")
.withIndex("by_token", q =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
if (!user) throw new Error("用户不存在");
// 3. 获取目标资源
const task = await ctx.db.get(args.taskId);
if (!task) throw new Error("任务不存在");
// 4. 权限校验
if (task.userId !== user._id) {
throw new Error("无操作权限");
}
// 5. 执行更新
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
调用外部API的动作函数
Create separate file for actions that need Node.js:
typescript
// 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 (without "use node"), and actions that need Node.js in (with "use node").
convex/tasks.tsconvex/taskActions.ts需要Node.js的动作函数请单独创建文件:
typescript
// convex/taskActions.ts
"use node"; // 使用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. 身份认证
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("未通过身份认证");
// 2. 通过查询函数获取数据
const task = await ctx.runQuery(api.tasks.getTask, {
taskId: args.taskId,
});
if (!task) throw new Error("任务不存在");
// 3. 调用外部服务(使用Node.js SDK)
await sendgrid.send({
to: identity.email,
from: "noreply@example.com",
subject: "任务提醒",
text: `别忘记:${task.text}`,
});
// 4. 通过变更函数更新数据
await ctx.runMutation(api.tasks.markReminderSent, {
taskId: args.taskId,
});
return true;
},
});注意: 将查询和变更函数放在(无需),需要Node.js的动作函数放在(带)。
convex/tasks.ts"use node"convex/taskActions.ts"use node"Internal Functions
内部函数
For backend-only functions (called by scheduler, other functions):
typescript
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" });
}
},
});仅后端调用的函数(由调度器或其他函数调用):
typescript
import { internalMutation } from "./_generated/server";
export const processExpiredTasks = internalMutation({
args: {},
handler: async (ctx) => {
// 无需身份认证 - 仅能从后端调用
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
检查清单
- defined with validators
args - defined with validator
returns - Authentication check ()
ctx.auth.getUserIdentity() - Authorization check (ownership/permissions)
- All promises awaited
- Indexed queries (no on queries)
.filter() - Error handling with descriptive messages
- Scheduled functions use not
internal.*api.* - If using Node.js APIs: at top of file
"use node" - If file has : Only actions (no queries/mutations)
"use node" - Actions in separate file from queries/mutations when using "use node"
- 使用验证器定义了
args - 使用验证器定义了
returns - 添加了身份认证检查()
ctx.auth.getUserIdentity() - 添加了权限校验(所有权/权限验证)
- 所有Promise都已await
- 查询使用了索引(未在查询中使用)
.filter() - 错误处理包含描述性信息
- 调度函数使用而非
internal.*api.* - 使用Node.js API时:文件顶部添加了
"use node" - 文件带有时:仅包含动作函数(无查询/变更函数)
"use node" - 需要Node.js的动作函数与查询/变更函数分文件存放