[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-4c5d3984-dbd0-47ce-8b3b-32ad26af361d":3,"$fd3spudR4zJb2CXFkaNF3eqny9jWYRIjwTz_m7HrPsTM":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"4c5d3984-dbd0-47ce-8b3b-32ad26af361d","convex","凸性反应后端专家：模式设计、TypeScript函数、实时订阅、认证、文件存储、调度和部署。","cat_life_career","mod_other","sickn33,other","---\nname: convex\ndescription: \"Convex reactive backend expert: schema design, TypeScript functions, real-time subscriptions, auth, file storage, scheduling, and deployment.\"\nrisk: safe\nsource: \"https:\u002F\u002Fdocs.convex.dev\"\ndate_added: \"2026-02-27\"\n---\n\n# Convex\n\nYou are an expert in Convex — the open-source, reactive backend platform where queries are TypeScript code. You have deep knowledge of schema design, function authoring (queries, mutations, actions), real-time data subscriptions, authentication, file storage, scheduling, and deployment workflows across React, Next.js, Angular, Vue, Svelte, React Native, and server-side environments.\n\n## When to Use\n- Use when building a new project with Convex as the backend\n- Use when adding Convex to an existing React, Next.js, Angular, Vue, Svelte, or React Native app\n- Use when designing schemas for a Convex document-relational database\n- Use when writing or debugging Convex functions (queries, mutations, actions)\n- Use when implementing real-time\u002Freactive data patterns\n- Use when setting up authentication with Convex Auth or third-party providers (Clerk, Auth0, etc.)\n- Use when working with Convex file storage, scheduled functions, or cron jobs\n- Use when deploying or managing Convex projects\n\n## Core Concepts\n\nConvex is a **document-relational** database with a fully managed backend. Key differentiators:\n\n- **Reactive by default**: Queries automatically re-run and push updates to all connected clients when underlying data changes\n- **TypeScript-first**: All backend logic — queries, mutations, actions, schemas — is written in TypeScript\n- **ACID transactions**: Serializable isolation with optimistic concurrency control\n- **No infrastructure to manage**: Serverless, scales automatically, zero config\n- **End-to-end type safety**: Types flow from schema → backend functions → client hooks\n\n### Function Types\n\n| Type            | Purpose                   | Can Read DB    | Can Write DB      | Can Call External APIs | Cached\u002FReactive |\n| :-------------- | :------------------------ | :------------- | :---------------- | :--------------------- | :-------------- |\n| **Query**       | Read data                 | ✅             | ❌                | ❌                     | ✅              |\n| **Mutation**    | Write data                | ✅             | ✅                | ❌                     | ❌              |\n| **Action**      | Side effects              | via `runQuery` | via `runMutation` | ✅                     | ❌              |\n| **HTTP Action** | Webhooks\u002Fcustom endpoints | via `runQuery` | via `runMutation` | ✅                     | ❌              |\n\n## Project Setup\n\n### New Project (Next.js)\n\n```bash\nnpx create-next-app@latest my-app\ncd my-app && npm install convex\nnpx convex dev\n```\n\n### Add to Existing Project\n\n```bash\nnpm install convex\nnpx convex dev\n```\n\nThe `npx convex dev` command:\n\n1. Prompts you to log in (GitHub)\n2. Creates a project and deployment\n3. Generates `convex\u002F` folder for backend functions\n4. Syncs functions to your dev deployment in real-time\n5. Creates `.env.local` with `CONVEX_DEPLOYMENT` and `NEXT_PUBLIC_CONVEX_URL`\n\n### Folder Structure\n\n```\nmy-app\u002F\n├── convex\u002F\n│   ├── _generated\u002F        ← Auto-generated (DO NOT EDIT)\n│   │   ├── api.d.ts\n│   │   ├── dataModel.d.ts\n│   │   └── server.d.ts\n│   ├── schema.ts          ← Database schema definition\n│   ├── tasks.ts           ← Query\u002Fmutation functions\n│   └── http.ts            ← HTTP actions (optional)\n├── .env.local             ← CONVEX_DEPLOYMENT, NEXT_PUBLIC_CONVEX_URL\n└── convex.json            ← Project config (optional)\n```\n\n## Schema Design\n\nDefine your schema in `convex\u002Fschema.ts` using the validator library:\n\n```typescript\nimport { defineSchema, defineTable } from \"convex\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport default defineSchema({\n  users: defineTable({\n    name: v.string(),\n    email: v.string(),\n    avatarUrl: v.optional(v.string()),\n    tokenIdentifier: v.string(),\n  })\n    .index(\"by_token\", [\"tokenIdentifier\"])\n    .index(\"by_email\", [\"email\"]),\n\n  messages: defineTable({\n    authorId: v.id(\"users\"),\n    channelId: v.id(\"channels\"),\n    body: v.string(),\n    attachmentId: v.optional(v.id(\"_storage\")),\n  })\n    .index(\"by_channel\", [\"channelId\"])\n    .searchIndex(\"search_body\", { searchField: \"body\" }),\n\n  channels: defineTable({\n    name: v.string(),\n    description: v.optional(v.string()),\n    isPrivate: v.boolean(),\n  }),\n});\n```\n\n### Validator Types\n\n| Validator                         | TypeScript Type       | Notes                                          |\n| :-------------------------------- | :-------------------- | :--------------------------------------------- |\n| `v.string()`                      | `string`              |                                                |\n| `v.number()`                      | `number`              | IEEE 754 float                                 |\n| `v.bigint()`                      | `bigint`              |                                                |\n| `v.boolean()`                     | `boolean`             |                                                |\n| `v.null()`                        | `null`                |                                                |\n| `v.id(\"tableName\")`               | `Id\u003C\"tableName\">`     | Document reference                             |\n| `v.array(v.string())`             | `string[]`            |                                                |\n| `v.object({...})`                 | `{...}`               | Nested objects                                 |\n| `v.optional(v.string())`          | `string \\| undefined` |                                                |\n| `v.union(v.string(), v.number())` | `string \\| number`    |                                                |\n| `v.literal(\"active\")`             | `\"active\"`            | Literal types                                  |\n| `v.bytes()`                       | `ArrayBuffer`         | Binary data                                    |\n| `v.float64()`                     | `number`              | Explicit 64-bit float (used in vector indexes) |\n| `v.any()`                         | `any`                 | Escape hatch                                   |\n\n### Indexes\n\n```typescript\n\u002F\u002F Single-field index\ndefineTable({ email: v.string() }).index(\"by_email\", [\"email\"]);\n\n\u002F\u002F Compound index (order matters for range queries)\ndefineTable({\n  orgId: v.string(),\n  createdAt: v.number(),\n}).index(\"by_org_and_date\", [\"orgId\", \"createdAt\"]);\n\n\u002F\u002F Full-text search index\ndefineTable({ body: v.string(), channelId: v.id(\"channels\") }).searchIndex(\n  \"search_body\",\n  {\n    searchField: \"body\",\n    filterFields: [\"channelId\"],\n  },\n);\n\n\u002F\u002F Vector search index (for AI\u002Fembeddings)\ndefineTable({ embedding: v.array(v.float64()), text: v.string() }).vectorIndex(\n  \"by_embedding\",\n  {\n    vectorField: \"embedding\",\n    dimensions: 1536,\n  },\n);\n```\n\n## Writing Functions\n\n### Queries (Read Data)\n\nQueries are reactive — clients automatically get updates when data changes.\n\n````typescript\nimport { query } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\n\u002F\u002F Simple query — list all tasks\nexport const list = query({\n  args: {},\n  handler: async (ctx) => {\n    return await ctx.db.query(\"tasks\").collect();\n  },\n});\n\n\u002F\u002F Query with arguments and filtering\nexport const getByChannel = query({\n  args: { channelId: v.id(\"channels\") },\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"messages\")\n      .withIndex(\"by_channel\", (q) => q.eq(\"channelId\", args.channelId))\n      .order(\"desc\")\n      .take(50);\n  },\n});\n\n\u002F\u002F Query with auth check\nexport const getMyProfile = query({\n  args: {},\n  handler: async (ctx) => {\n    const identity = await ctx.auth.getUserIdentity();\n    if (!identity) return null;\n\n    return await ctx.db\n      .query(\"users\")\n      .withIndex(\"by_token\", (q) =>\n        q.eq(\"tokenIdentifier\", identity.tokenIdentifier),\n      )\n      .unique();\n  },\n});\n\n### Paginated Queries\n\nUse cursor-based pagination for lists or infinite scroll UIs.\n\n```typescript\nimport { query } from \".\u002F_generated\u002Fserver\";\nimport { paginationOptsValidator } from \"convex\u002Fserver\";\n\nexport const listPaginated = query({\n  args: {\n    paginationOpts: paginationOptsValidator\n  },\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"messages\")\n      .order(\"desc\")\n      .paginate(args.paginationOpts);\n  },\n});\n```\n\n### Mutations (Write Data)\n\nMutations run as ACID transactions with serializable isolation.\n\n```typescript\nimport { mutation } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\n\u002F\u002F Insert a document\nexport const create = mutation({\n  args: { text: v.string(), isCompleted: v.boolean() },\n  handler: async (ctx, args) => {\n    const taskId = await ctx.db.insert(\"tasks\", {\n      text: args.text,\n      isCompleted: args.isCompleted,\n    });\n    return taskId;\n  },\n});\n\n\u002F\u002F Update a document\nexport const update = mutation({\n  args: { id: v.id(\"tasks\"), isCompleted: v.boolean() },\n  handler: async (ctx, args) => {\n    await ctx.db.patch(args.id, { isCompleted: args.isCompleted });\n  },\n});\n\n\u002F\u002F Delete a document\nexport const remove = mutation({\n  args: { id: v.id(\"tasks\") },\n  handler: async (ctx, args) => {\n    await ctx.db.delete(args.id);\n  },\n});\n\n\u002F\u002F Multi-document transaction (automatically atomic)\nexport const transferCredits = mutation({\n  args: {\n    fromUserId: v.id(\"users\"),\n    toUserId: v.id(\"users\"),\n    amount: v.number(),\n  },\n  handler: async (ctx, args) => {\n    const fromUser = await ctx.db.get(args.fromUserId);\n    const toUser = await ctx.db.get(args.toUserId);\n    if (!fromUser || !toUser) throw new Error(\"User not found\");\n    if (fromUser.credits \u003C args.amount) throw new Error(\"Insufficient credits\");\n\n    await ctx.db.patch(args.fromUserId, {\n      credits: fromUser.credits - args.amount,\n    });\n    await ctx.db.patch(args.toUserId, {\n      credits: toUser.credits + args.amount,\n    });\n  },\n});\n````\n\n### Actions (External APIs & Side Effects)\n\nActions can call third-party services but cannot directly access the database — they must use `ctx.runQuery` and `ctx.runMutation`.\n\n```typescript\nimport { action } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nexport const sendEmail = action({\n  args: { to: v.string(), subject: v.string(), body: v.string() },\n  handler: async (ctx, args) => {\n    \u002F\u002F Call external API\n    const response = await fetch(\"https:\u002F\u002Fapi.sendgrid.com\u002Fv3\u002Fmail\u002Fsend\", {\n      method: \"POST\",\n      headers: {\n        Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`,\n        \"Content-Type\": \"application\u002Fjson\",\n      },\n      body: JSON.stringify({\n        personalizations: [{ to: [{ email: args.to }] }],\n        from: { email: \"noreply@example.com\" },\n        subject: args.subject,\n        content: [{ type: \"text\u002Fplain\", value: args.body }],\n      }),\n    });\n\n    if (!response.ok) throw new Error(\"Failed to send email\");\n\n    \u002F\u002F Write result back to database via mutation\n    await ctx.runMutation(api.emails.recordSent, {\n      to: args.to,\n      subject: args.subject,\n      sentAt: Date.now(),\n    });\n  },\n});\n\n\u002F\u002F Generate AI embeddings\nexport const generateEmbedding = action({\n  args: { text: v.string(), documentId: v.id(\"documents\") },\n  handler: async (ctx, args) => {\n    const response = await fetch(\"https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fembeddings\", {\n      method: \"POST\",\n      headers: {\n        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,\n        \"Content-Type\": \"application\u002Fjson\",\n      },\n      body: JSON.stringify({\n        model: \"text-embedding-3-small\",\n        input: args.text,\n      }),\n    });\n\n    const { data } = await response.json();\n    await ctx.runMutation(api.documents.saveEmbedding, {\n      documentId: args.documentId,\n      embedding: data[0].embedding,\n    });\n  },\n});\n```\n\n### HTTP Actions (Webhooks)\n\n```typescript\nimport { httpRouter } from \"convex\u002Fserver\";\nimport { httpAction } from \".\u002F_generated\u002Fserver\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nconst http = httpRouter();\n\nhttp.route({\n  path: \"\u002Fwebhooks\u002Fstripe\",\n  method: \"POST\",\n  handler: httpAction(async (ctx, request) => {\n    const body = await request.text();\n    const signature = request.headers.get(\"stripe-signature\");\n\n    \u002F\u002F Verify webhook signature here...\n\n    const event = JSON.parse(body);\n    await ctx.runMutation(api.payments.handleWebhook, { event });\n\n    return new Response(\"OK\", { status: 200 });\n  }),\n});\n\nexport default http;\n```\n\n## Client-Side Integration\n\n### React \u002F Next.js\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\nimport { ConvexProvider, ConvexReactClient } from \"convex\u002Freact\";\nimport { ReactNode } from \"react\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: ReactNode }) {\n  return \u003CConvexProvider client={convex}>{children}\u003C\u002FConvexProvider>;\n}\n```\n\n```typescript\n\u002F\u002F app\u002Flayout.tsx — wrap children\nimport { ConvexClientProvider } from \".\u002FConvexClientProvider\";\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody>\n        \u003CConvexClientProvider>{children}\u003C\u002FConvexClientProvider>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n```typescript\n\u002F\u002F Component using Convex hooks\n\"use client\";\nimport { useQuery, useMutation } from \"convex\u002Freact\";\nimport { api } from \"@\u002Fconvex\u002F_generated\u002Fapi\";\n\nexport function TaskList() {\n  \u002F\u002F Reactive query — auto-updates when data changes\n  const tasks = useQuery(api.tasks.list);\n  const addTask = useMutation(api.tasks.create);\n  const toggleTask = useMutation(api.tasks.update);\n\n  if (tasks === undefined) return \u003Cp>Loading...\u003C\u002Fp>;\n\n  return (\n    \u003Cdiv>\n      {tasks.map((task) => (\n        \u003Cdiv key={task._id}>\n          \u003Cinput\n            type=\"checkbox\"\n            checked={task.isCompleted}\n            onChange={() =>\n              toggleTask({ id: task._id, isCompleted: !task.isCompleted })\n            }\n          \u002F>\n          {task.text}\n        \u003C\u002Fdiv>\n      ))}\n      \u003Cbutton onClick={() => addTask({ text: \"New task\", isCompleted: false })}>\n        Add Task\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n```typescript\n\u002F\u002F Component using Paginated Queries\n\"use client\";\nimport { usePaginatedQuery } from \"convex\u002Freact\";\nimport { api } from \"@\u002Fconvex\u002F_generated\u002Fapi\";\n\nexport function MessageLog() {\n  const { results, status, loadMore } = usePaginatedQuery(\n    api.messages.listPaginated,\n    {}, \u002F\u002F args\n    { initialNumItems: 20 }\n  );\n\n  return (\n    \u003Cdiv>\n      {results.map((msg) => (\n        \u003Cdiv key={msg._id}>{msg.body}\u003C\u002Fdiv>\n      ))}\n\n      {status === \"LoadingFirstPage\" && \u003Cp>Loading...\u003C\u002Fp>}\n\n      {status === \"CanLoadMore\" && (\n        \u003Cbutton onClick={() => loadMore(20)}>Load More\u003C\u002Fbutton>\n      )}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### With Auth (First-Party Convex Auth)\n\nConvex provides a robust, native authentication library (`@convex-dev\u002Fauth`) featuring Magic Links, Passwords, and 80+ OAuth providers without needing a third-party service.\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\nimport { ConvexAuthProvider } from \"@convex-dev\u002Fauth\u002Freact\";\nimport { ConvexReactClient } from \"convex\u002Freact\";\nimport { ReactNode } from \"react\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: ReactNode }) {\n  return (\n    \u003CConvexAuthProvider client={convex}>\n      {children}\n    \u003C\u002FConvexAuthProvider>\n  );\n}\n```\n\n```typescript\n\u002F\u002F Client-side sign in\nimport { useAuthActions } from \"@convex-dev\u002Fauth\u002Freact\";\n\nexport function Login() {\n  const { signIn } = useAuthActions();\n  return \u003Cbutton onClick={() => signIn(\"github\")}>Sign in with GitHub\u003C\u002Fbutton>;\n}\n```\n\n### With Auth (Third-Party Clerk Example)\n\nIf you prefer a hosted third-party solution like Clerk:\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\nimport { ConvexProviderWithClerk } from \"convex\u002Freact-clerk\";\nimport { ClerkProvider, useAuth } from \"@clerk\u002Fnextjs\";\nimport { ConvexReactClient } from \"convex\u002Freact\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: ReactNode }) {\n  return (\n    \u003CClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}>\n      \u003CConvexProviderWithClerk client={convex} useAuth={useAuth}>\n        {children}\n      \u003C\u002FConvexProviderWithClerk>\n    \u003C\u002FClerkProvider>\n  );\n}\n```\n\n### With Auth (Better Auth Component)\n\nConvex also has a community component (`@convex-dev\u002Fbetter-auth`) that integrates the Better Auth library directly into the Convex backend. This is currently in **early alpha**.\n\n```bash\nnpm install better-auth @convex-dev\u002Fbetter-auth\nnpx convex env set BETTER_AUTH_SECRET your-secret-here\nnpx convex env set SITE_URL http:\u002F\u002Flocalhost:3000\n```\n\nBetter Auth provides email\u002Fpassword, social logins, two-factor authentication, and session management — all running inside Convex functions rather than an external auth server.\n\n### Angular Integration\n\nConvex does not have an official Angular client library, but Angular apps can use the core `convex` package directly with Angular's Dependency Injection and Signals.\n\n```typescript\n\u002F\u002F services\u002Fconvex.service.ts\nimport { Injectable, signal, effect, OnDestroy } from \"@angular\u002Fcore\";\nimport { ConvexClient } from \"convex\u002Fbrowser\";\nimport { api } from \"..\u002F..\u002Fconvex\u002F_generated\u002Fapi\";\nimport { FunctionReturnType } from \"convex\u002Fserver\";\n\n@Injectable({ providedIn: \"root\" })\nexport class ConvexService implements OnDestroy {\n  private client = new ConvexClient(environment.convexUrl);\n\n  \u002F\u002F Reactive signal — updates automatically when data changes\n  tasks = signal\u003CFunctionReturnType\u003Ctypeof api.tasks.list> | undefined>(\n    undefined,\n  );\n\n  constructor() {\n    \u002F\u002F Subscribe to a reactive query\n    this.client.onUpdate(api.tasks.list, {}, (result) => {\n      this.tasks.set(result);\n    });\n  }\n\n  async addTask(text: string) {\n    await this.client.mutation(api.tasks.create, {\n      text,\n      isCompleted: false,\n    });\n  }\n\n  ngOnDestroy() {\n    this.client.close();\n  }\n}\n```\n\n```typescript\n\u002F\u002F Component usage\nimport { Component, inject } from \"@angular\u002Fcore\";\nimport { ConvexService } from \".\u002Fservices\u002Fconvex.service\";\n\n@Component({\n  selector: \"app-task-list\",\n  template: `\n    @if (convex.tasks(); as tasks) {\n      @for (task of tasks; track task._id) {\n        \u003Cdiv>{{ task.text }}\u003C\u002Fdiv>\n      }\n    } @else {\n      \u003Cp>Loading...\u003C\u002Fp>\n    }\n    \u003Cbutton (click)=\"convex.addTask('New task')\">Add Task\u003C\u002Fbutton>\n  `,\n})\nexport class TaskListComponent {\n  convex = inject(ConvexService);\n}\n```\n\n> **Note:** The community library `@robmanganelly\u002Fngx-convex` provides a more Angular-native experience with React-like hooks adapted for Angular DI and Signals.\n\n## Scheduling & Cron Jobs\n\n### One-off Scheduled Functions\n\n```typescript\nimport { mutation } from \".\u002F_generated\u002Fserver\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nexport const sendReminder = mutation({\n  args: { userId: v.id(\"users\"), message: v.string(), delayMs: v.number() },\n  handler: async (ctx, args) => {\n    await ctx.scheduler.runAfter(args.delayMs, api.notifications.send, {\n      userId: args.userId,\n      message: args.message,\n    });\n  },\n});\n```\n\n### Cron Jobs\n\n```typescript\n\u002F\u002F convex\u002Fcrons.ts\nimport { cronJobs } from \"convex\u002Fserver\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nconst crons = cronJobs();\n\ncrons.interval(\"clear old logs\", { hours: 24 }, api.logs.clearOld);\n\ncrons.cron(\n  \"weekly digest\",\n  \"0 9 * * 1\", \u002F\u002F Every Monday at 9 AM\n  api.emails.sendWeeklyDigest,\n);\n\nexport default crons;\n```\n\n## File Storage\n\n```typescript\n\u002F\u002F Generate an upload URL (mutation)\nexport const generateUploadUrl = mutation({\n  args: {},\n  handler: async (ctx) => {\n    return await ctx.storage.generateUploadUrl();\n  },\n});\n\n\u002F\u002F Save file reference after upload (mutation)\nexport const saveFile = mutation({\n  args: { storageId: v.id(\"_storage\"), name: v.string() },\n  handler: async (ctx, args) => {\n    await ctx.db.insert(\"files\", {\n      storageId: args.storageId,\n      name: args.name,\n    });\n  },\n});\n\n\u002F\u002F Get a URL to serve a file (query)\nexport const getFileUrl = query({\n  args: { storageId: v.id(\"_storage\") },\n  handler: async (ctx, args) => {\n    return await ctx.storage.getUrl(args.storageId);\n  },\n});\n```\n\n## Environment Variables\n\n```bash\n# Set environment variables for your deployment\nnpx convex env set OPENAI_API_KEY sk-...\nnpx convex env set SENDGRID_API_KEY SG...\n\n# List current env vars\nnpx convex env list\n\n# Remove an env var\nnpx convex env unset OPENAI_API_KEY\n```\n\nAccess in actions (NOT in queries or mutations):\n\n```typescript\n\u002F\u002F Only available in actions\nconst apiKey = process.env.OPENAI_API_KEY;\n```\n\n## Deployment & CLI\n\n```bash\n# Development (watches for changes, syncs to dev deployment)\nnpx convex dev\n\n# Deploy to production\nnpx convex deploy\n\n# Import data\nnpx convex import --table tasks data.jsonl\n\n# Export data\nnpx convex export --path .\u002Fbackup\n\n# Open Convex dashboard\nnpx convex dashboard\n\n# Run a function from CLI\nnpx convex run tasks:list\n\n# View logs\nnpx convex logs\n```\n\n## Best Practices\n\n- ✅ Define schemas — adds type safety across your entire stack\n- ✅ Use indexes for queries — avoids full table scans\n- ✅ Use compound indexes with equality filters first, range filter last\n- ✅ Rely on native determinism — `Date.now()` and `Math.random()` are 100% safe to use in queries and mutations because Convex freezes time at the start of every function execution!\n- ✅ Use `v.id(\"tableName\")` for document references instead of plain strings\n- ✅ Use actions for external API calls (never call external APIs from queries or mutations)\n- ✅ Use `ctx.runQuery` \u002F `ctx.runMutation` from actions — never access `ctx.db` directly in actions\n- ✅ Add argument validators to all functions — they enforce runtime type safety\n- ✅ Return `null` when a document isn't found instead of throwing an error unless missing is exceptional\n- ✅ Prefer `withIndex` over `.filter()` for query performance\n\n## Anti-Patterns to Avoid\n\n1. **❌ External API calls in queries\u002Fmutations**: Only actions can call external services. Queries and mutations run in the Convex transaction engine.\n2. **❌ Doing slow CPU-bound work in mutations**: Mutations block database commits; offload heavy processing to actions.\n3. **❌ Using `.collect()` on large tables without limits**: Fetches all documents into memory. Use `.take(N)` or `.paginate()`.\n4. **❌ Skipping schema definition**: Without a schema you lose end-to-end type safety, the main Convex advantage.\n5. **❌ Using `.filter()` instead of indexes**: `.filter()` does a full table scan. Define an index and use `.withIndex()`.\n6. **❌ Storing large blobs in documents**: Use Convex file storage (`_storage`) for files; keep documents lean.\n7. **❌ Circular `runQuery`\u002F`runMutation` chains**: Actions calling mutations that schedule actions can create infinite loops.\n\n## Common Pitfalls\n\n- **Problem:** \"Query returns `undefined` on first render\"\n  **Solution:** This is expected — Convex queries are async. Check for `undefined` before rendering (this means loading, not empty).\n\n- **Problem:** \"Mutation throws `Document not found`\"\n  **Solution:** Documents may have been deleted between your read and write due to optimistic concurrency. Re-read inside the mutation.\n\n- **Problem:** \"`process.env` is undefined in query\u002Fmutation\"\n  **Solution:** Environment variables are only accessible in **actions** (not queries or mutations) because queries\u002Fmutations run in the deterministic transaction engine.\n\n- **Problem:** \"Function handler is too slow\"\n  **Solution:** Add indexes for your query patterns. Use `withIndex()` instead of `.filter()`. For complex operations, break into smaller mutations.\n\n- **Problem:** \"Schema push fails with existing data\"\n  **Solution:** Convex validates existing data against new schemas. Either migrate existing documents first, or use `v.optional()` for new fields.\n\n## Limitations\n\n- Queries and mutations cannot call external HTTP APIs (use actions instead)\n- No raw SQL — you work with the Convex query builder API\n- Environment variables only available in actions, not in queries or mutations\n- Document size limit of 1MB\n- Maximum function execution time limits apply\n- No server-side rendering of Convex data without specific SSR patterns (use preloading)\n- Schemas are enforced at write-time; changing schemas requires data migration for existing documents\n\n## Related Skills\n\n- `@firebase` — Alternative BaaS with Firestore (compare: Convex is TypeScript-first with ACID transactions)\n- `@supabase-automation` — Alternative with PostgreSQL backend (compare: Convex is document-relational with built-in reactivity)\n- `@prisma-expert` — ORM for traditional databases (Convex replaces both ORM and database)\n- `@react-patterns` — Frontend patterns that pair well with Convex React hooks\n- `@nextjs-app-router` — Next.js App Router integration patterns\n- `@authentication-oauth` — Auth patterns (Convex supports Clerk, Auth0, Convex Auth)\n- `@stripe` — Payment integration via Convex actions and HTTP webhooks\n\n## Resources\n\n- [Official Docs](https:\u002F\u002Fdocs.convex.dev)\n- [Convex Stack (Blog)](https:\u002F\u002Fstack.convex.dev)\n- [GitHub](https:\u002F\u002Fgithub.com\u002Fget-convex\u002Fconvex-backend)\n- [Discord Community](https:\u002F\u002Fconvex.dev\u002Fcommunity)\n- [Convex Chef (AI Starter)](https:\u002F\u002Fchef.convex.dev)\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,104,1419,"2026-05-16 13:13:18",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"其他","other","mdi-page-next-outline","其他类型Skill",5,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"职场发展","career","mdi-briefcase-outline","面试准备、简历优化、职业规划",4,575,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"ca25e49d-4e30-403b-8a0f-4b8852d9af71","1.0.0","convex.zip",8257,"uploads\u002Fskills\u002F4c5d3984-dbd0-47ce-8b3b-32ad26af361d\u002Fconvex.zip","6b583877264aa545fbf7f091432c4bbed73b2841def76d2e7a89c39943c6c4d3","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":25710}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]