Loading...
Loading...
Compare original and translation side by side
| Type | Purpose | Database | External APIs | Deterministic |
|---|---|---|---|---|
| Read data | ✅ Read | ❌ | ✅ Required |
| Write data | ✅ Read/Write | ❌ | ✅ Required |
| Side effects | Via | ✅ | ❌ |
| HTTP endpoints | Via ctx | ✅ | ❌ |
| 类型 | 用途 | 数据库访问 | 外部API调用 | 是否确定性 |
|---|---|---|---|---|
| 读取数据 | ✅ 支持读取 | ❌ 不支持 | ✅ 必须是确定性 |
| 写入数据 | ✅ 支持读写 | ❌ 不支持 | ✅ 必须是确定性 |
| 处理副作用 | 通过 | ✅ 支持 | ❌ 非确定性 |
| HTTP端点 | 通过ctx支持 | ✅ 支持 | ❌ 非确定性 |
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
users: defineTable({
name: v.string(),
email: v.string(),
role: v.union(v.literal("admin"), v.literal("user")),
profileId: v.optional(v.id("profiles")),
})
.index("by_email", ["email"])
.index("by_role", ["role"]),
messages: defineTable({
authorId: v.id("users"),
body: v.string(),
channel: v.string(),
})
.index("by_channel", ["channel"])
.searchIndex("search_body", { searchField: "body" }),
});// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
users: defineTable({
name: v.string(),
email: v.string(),
role: v.union(v.literal("admin"), v.literal("user")),
profileId: v.optional(v.id("profiles")),
})
.index("by_email", ["email"])
.index("by_role", ["role"]),
messages: defineTable({
authorId: v.id("users"),
body: v.string(),
channel: v.string(),
})
.index("by_channel", ["channel"])
.searchIndex("search_body", { searchField: "body" }),
});// convex/messages.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
export const list = query({
args: { channel: v.string(), limit: v.optional(v.number()) },
handler: async (ctx, args) => {
return await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channel", args.channel))
.order("desc")
.take(args.limit ?? 50);
},
});// convex/messages.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
export const list = query({
args: { channel: v.string(), limit: v.optional(v.number()) },
handler: async (ctx, args) => {
return await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channel", args.channel))
.order("desc")
.take(args.limit ?? 50);
},
});// convex/messages.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const send = mutation({
args: { body: v.string(), channel: v.string() },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
return await ctx.db.insert("messages", {
authorId: identity.subject,
body: args.body,
channel: args.channel,
});
},
});// convex/messages.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const send = mutation({
args: { body: v.string(), channel: v.string() },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
return await ctx.db.insert("messages", {
authorId: identity.subject,
body: args.body,
channel: args.channel,
});
},
});// convex/ai.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api, internal } from "./_generated/api";
export const generateResponse = action({
args: { prompt: v.string(), threadId: v.id("threads") },
handler: async (ctx, args) => {
// Call external API
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: args.prompt }] }),
});
const data = await response.json();
// Write result via mutation
await ctx.runMutation(internal.messages.saveAIResponse, {
threadId: args.threadId,
content: data.choices[0].message.content,
});
},
});// convex/ai.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api, internal } from "./_generated/api";
export const generateResponse = action({
args: { prompt: v.string(), threadId: v.id("threads") },
handler: async (ctx, args) => {
// 调用外部API
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: args.prompt }] }),
});
const data = await response.json();
// 通过变更函数写入结果
await ctx.runMutation(internal.messages.saveAIResponse, {
threadId: args.threadId,
content: data.choices[0].message.content,
});
},
});// app/page.tsx
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
export default function Chat() {
const messages = useQuery(api.messages.list, { channel: "general" });
const sendMessage = useMutation(api.messages.send);
if (messages === undefined) return <div>Loading...</div>;
return (
<div>
{messages.map((msg) => <p key={msg._id}>{msg.body}</p>)}
<button onClick={() => sendMessage({ body: "Hello!", channel: "general" })}>
Send
</button>
</div>
);
}// app/page.tsx
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
export default function Chat() {
const messages = useQuery(api.messages.list, { channel: "general" });
const sendMessage = useMutation(api.messages.send);
if (messages === undefined) return <div>加载中...</div>;
return (
<div>
{messages.map((msg) => <p key={msg._id}>{msg.body}</p>)}
<button onClick={() => sendMessage({ body: "Hello!", channel: "general" })}>
发送
</button>
</div>
);
}useQueryuseQueryMath.random()Date.now()fetch_creationTimeDate.now()Math.random()Date.now()fetch_creationTimeDate.now()Doc<"tableName">Id<"tableName">Doc<"tableName">Id<"tableName">| Validator | TypeScript Type | Example |
|---|---|---|
| | |
| | |
| | |
| | |
| | Document ID |
| | |
| | |
| | Optional field |
| | Union type |
| | Exact value |
| | Any value |
| | Binary data |
| | Key-value map |
| 验证器 | TypeScript类型 | 示例 |
|---|---|---|
| | |
| | |
| | |
| | |
| | 文档ID |
| | |
| | |
| | 可选字段 |
| | 联合类型 |
| | 精确值 |
| | 任意值 |
| | 二进制数据 |
| | 键值对映射 |
Need to read data?
├─ Yes → query
│ └─ Need real-time updates? → useQuery (React)
│ └─ One-time fetch? → fetchQuery (Server)
└─ No
└─ Need to write data?
├─ Yes → mutation
│ └─ Also need external API? → mutation + scheduler.runAfter(0, action)
└─ No
└─ Need external API? → action
└─ Need durability? → Workflow component需要读取数据?
├─ 是 → 使用query
│ └─ 需要实时更新? → 使用useQuery(React端)
│ └─ 一次性获取? → 使用fetchQuery(服务端)
└─ 否
└─ 需要写入数据?
├─ 是 → 使用mutation
│ └─ 同时需要调用外部API? → mutation + scheduler.runAfter(0, action)
└─ 否
└─ 需要调用外部API? → 使用action
└─ 需要持久性? → 使用工作流组件| Use Case | Function Type |
|---|---|
| Client can call | |
| Backend only | |
| Scheduled work | Internal functions |
| Security-sensitive | Internal functions |
| 使用场景 | 函数类型 |
|---|---|
| 客户端可直接调用 | |
| 仅后端可用 | |
| 定时任务 | 内部函数 |
| 安全敏感逻辑 | 内部函数 |
my-app/
├── convex/
│ ├── _generated/ # Auto-generated (don't edit)
│ │ ├── api.d.ts
│ │ ├── api.js
│ │ ├── dataModel.d.ts
│ │ └── server.d.ts
│ ├── schema.ts # Database schema
│ ├── auth.ts # Auth configuration
│ ├── users.ts # User functions
│ ├── messages.ts # Message functions
│ ├── crons.ts # Scheduled jobs
│ ├── http.ts # HTTP endpoints
│ └── model/ # Business logic (recommended)
│ ├── users.ts
│ └── messages.ts
├── app/ # Next.js app
│ ├── ConvexClientProvider.tsx
│ └── page.tsx
└── .env.local
└── NEXT_PUBLIC_CONVEX_URL=...my-app/
├── convex/
│ ├── _generated/ # 自动生成(请勿编辑)
│ │ ├── api.d.ts
│ │ ├── api.js
│ │ ├── dataModel.d.ts
│ │ └── server.d.ts
│ ├── schema.ts # 数据库模式文件
│ ├── auth.ts # 身份验证配置
│ ├── users.ts # 用户相关函数
│ ├── messages.ts # 消息相关函数
│ ├── crons.ts # 定时任务
│ ├── http.ts # HTTP端点
│ └── model/ # 业务逻辑(推荐目录)
│ ├── users.ts
│ └── messages.ts
├── app/ # Next.js应用目录
│ ├── ConvexClientProvider.tsx
│ └── page.tsx
└── .env.local
└── NEXT_PUBLIC_CONVEX_URL=...undefinedundefinedundefinedundefinedundefinedundefined
Access in functions:
```typescript
const apiKey = process.env.OPENAI_API_KEY;
在函数中访问环境变量:
```typescript
const apiKey = process.env.OPENAI_API_KEY;