Created
October 12, 2025 15:41
-
-
Save robinebers/5a316c6206c7cf6ba44cf8537b1f8152 to your computer and use it in GitHub Desktop.
Must-follow patterns for Convex mutations, queries, and usage of `api.` or `internal.`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Must-follow patterns for Convex mutations, queries, and usage of `api.` or `internal.` | |
| alwaysApply: false | |
| --- | |
| # Convex API Reference Patterns | |
| ## Overview | |
| To avoid TypeScript deep instantiation errors (TS2589) when working with Convex's generated API objects, we use lightweight function references created with `makeFunctionReference` instead of importing `api` or `internal` from `convex/_generated/api`. | |
| ## The Problem | |
| When the Convex API grows large, TypeScript's type resolver hits its recursion limit when trying to resolve types from the generated `api` and `internal` objects: | |
| ``` | |
| Type instantiation is excessively deep and possibly infinite. TS2589 | |
| ``` | |
| This happens when: | |
| - Using `FunctionReturnType<typeof api.*>` in components | |
| - Passing `api.*` references directly to hooks like `useQuery`, `useMutation`, `useAction` | |
| - Calling Convex functions via `ctx.runQuery`/`ctx.runMutation`/`ctx.runAction` | |
| - Having a large number of Convex functions in your project | |
| ## The Solution | |
| We use `makeFunctionReference` with explicit function paths to create lightweight function references that don't trigger deep type resolution. | |
| ### Directory Structure | |
| ``` | |
| convex/refs/ | |
| ├── api.ts # Public API function references | |
| ├── internal.ts # Internal API function references | |
| └── webhooks.ts # Webhook function references | |
| ``` | |
| ## Usage Patterns | |
| ### 1. Client Components (React Hooks) | |
| **❌ DON'T:** | |
| ```typescript | |
| import { api } from '@/convex/_generated/api'; | |
| import { useQuery, useMutation, useAction } from 'convex/react'; | |
| // Causes deep instantiation errors | |
| const courses = useQuery(api.courses.list); | |
| const createCourse = useMutation(api.courses.create); | |
| const generateDescription = useAction(api.lessons.generateDescription); | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| import { apiRefs } from '@/convex/refs/api'; | |
| import { useQuery, useMutation, useAction } from 'convex/react'; | |
| // Uses lightweight references | |
| const courses = useQuery(apiRefs.courses.list); | |
| const createCourse = useMutation(apiRefs.courses.create); | |
| const generateDescription = useAction(apiRefs.lessons.generateDescription); | |
| ``` | |
| ### 2. Server Components & API Routes | |
| **❌ DON'T:** | |
| ```typescript | |
| import { api } from '@/convex/_generated/api'; | |
| import { createAuthenticatedConvexClient } from '@/lib/convex/server'; | |
| const convex = await createAuthenticatedConvexClient(); | |
| const data = await convex.query(api.courses.getDashboardData, {}); | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| import { apiRefs } from '@/convex/refs/api'; | |
| import { createAuthenticatedConvexClient } from '@/lib/convex/server'; | |
| const convex = await createAuthenticatedConvexClient(); | |
| const data = await convex.query(apiRefs.courses.getDashboardData, {}); | |
| ``` | |
| ### 3. Convex Functions (Actions calling queries/mutations) | |
| **❌ DON'T:** | |
| ```typescript | |
| import { internal } from "./_generated/api"; | |
| import { action } from "./_generated/server"; | |
| export const myAction = action({ | |
| handler: async (ctx, args) => { | |
| // Causes deep instantiation errors | |
| const user = await ctx.runQuery(internal.users.getCurrent); | |
| await ctx.runMutation(internal.messages.create, { text: "Hello" }); | |
| }, | |
| }); | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| import { internalRefs } from "@/convex/refs/internal"; | |
| import { action } from "./_generated/server"; | |
| export const myAction = action({ | |
| handler: async (ctx, args) => { | |
| // Uses lightweight references | |
| const user = await ctx.runQuery(internalRefs.users.getCurrent); | |
| await ctx.runMutation(internalRefs.messages.create, { text: "Hello" }); | |
| }, | |
| }); | |
| ``` | |
| ### 4. Type Annotations | |
| **❌ DON'T:** | |
| ```typescript | |
| import { FunctionReturnType } from "convex/server"; | |
| import { api } from "@/convex/_generated/api"; | |
| // Causes deep instantiation errors | |
| type CourseData = FunctionReturnType<typeof api.courses.getBySlug>; | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| // Define explicit types or use any with eslint-disable | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| type CourseData = any; | |
| // Or use the Convex-generated Doc types | |
| import { Doc } from "@/convex/_generated/dataModel"; | |
| type CourseData = Doc<"courses">; | |
| ``` | |
| ### 5. Preloaded Queries (Next.js) | |
| **❌ DON'T:** | |
| ```typescript | |
| import { api } from '@/convex/_generated/api'; | |
| import { preloadQuery } from 'convex/nextjs'; | |
| const preloaded = await preloadQuery(api.courses.getBySlug, { slug }); | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| import { apiRefs } from '@/convex/refs/api'; | |
| import { preloadQuery } from 'convex/nextjs'; | |
| const preloaded = await preloadQuery(apiRefs.courses.getBySlug, { slug }); | |
| ``` | |
| ## Adding New Function References | |
| When you create a new Convex function, add it to the appropriate refs file: | |
| ### For Public Functions (`convex/refs/api.ts`) | |
| ```typescript | |
| export const apiRefs = { | |
| // ... existing refs | |
| myNewModule: { | |
| myQuery: ref("myNewModule:myQuery", "query"), | |
| myMutation: ref("myNewModule:myMutation", "mutation"), | |
| myAction: ref("myNewModule:myAction", "action"), | |
| }, | |
| } as const; | |
| ``` | |
| ### For Internal Functions (`convex/refs/internal.ts`) | |
| ```typescript | |
| export const internalRefs = { | |
| // ... existing refs | |
| myModule: { | |
| internalQuery: ref("model/myModule/internal:internalQuery", "query"), | |
| internalMutation: ref("model/myModule/internal:internalMutation", "mutation"), | |
| }, | |
| } as const; | |
| ``` | |
| ## Function Path Format | |
| Function paths follow Convex's file-based routing convention: | |
| ### Top-level modules | |
| - File: `convex/courses.ts` | |
| - Export: `export const getBySlug = query({...})` | |
| - Path: `"courses:getBySlug"` | |
| ### Nested modules | |
| - File: `convex/integrations/mux.ts` | |
| - Export: `export const createUpload = action({...})` | |
| - Path: `"integrations/mux:createUpload"` | |
| ### Deep nesting | |
| - File: `convex/model/profiles/internal.ts` | |
| - Export: `export const findByEmail = internalQuery({...})` | |
| - Path: `"model/profiles/internal:findByEmail"` | |
| ### Special cases | |
| - Subdirectories with index files: Use the directory name | |
| - Nested actions: Include the full path with forward slashes | |
| ## Helper Function Pattern (PREFERRED) | |
| Instead of calling Convex functions from other Convex functions using `ctx.run*`, prefer using **helper functions** as recommended by Convex best practices: | |
| **❌ AVOID:** | |
| ```typescript | |
| // convex/users.ts | |
| export const getCurrentUser = internalQuery({ | |
| handler: async (ctx) => { | |
| const identity = await ctx.auth.getUserIdentity(); | |
| return await ctx.db.query("users").withIndex("by_clerk_id", ...).unique(); | |
| }, | |
| }); | |
| // convex/messages.ts | |
| export const sendMessage = mutation({ | |
| handler: async (ctx, args) => { | |
| // Requires internal ref and separate transaction | |
| const user = await ctx.runQuery(internalRefs.users.getCurrentUser); | |
| await ctx.db.insert("messages", { userId: user._id, ...args }); | |
| }, | |
| }); | |
| ``` | |
| **✅ PREFER:** | |
| ```typescript | |
| // convex/helpers/users.ts (NOT registered as a Convex function) | |
| import { QueryCtx } from "../_generated/server"; | |
| export async function getCurrentUser(ctx: QueryCtx) { | |
| const identity = await ctx.auth.getUserIdentity(); | |
| return await ctx.db.query("users").withIndex("by_clerk_id", ...).unique(); | |
| } | |
| // convex/messages.ts | |
| import { getCurrentUser } from "./helpers/users"; | |
| export const sendMessage = mutation({ | |
| handler: async (ctx, args) => { | |
| // Same transaction, no ref needed, cleaner code | |
| const user = await getCurrentUser(ctx); | |
| await ctx.db.insert("messages", { userId: user._id, ...args }); | |
| }, | |
| }); | |
| ``` | |
| **Benefits of helper functions:** | |
| - Execute in the same transaction | |
| - No need for function references | |
| - Reduced TypeScript complexity | |
| - Better performance (no extra function calls) | |
| - Easier to refactor | |
| **When to use `ctx.run*` with refs:** | |
| - Calling functions from actions (different runtime) | |
| - Need partial rollback (catch errors in `ctx.runMutation`) | |
| - Using Convex Components | |
| - Scheduling functions with `ctx.scheduler` | |
| ## Common Pitfalls | |
| ### 1. Using `anyApi` without explicit paths | |
| ```typescript | |
| // ❌ This doesn't work at runtime | |
| const ref = anyApi as FunctionReference<"query", "public", any, any>; | |
| // ✅ Must use makeFunctionReference with explicit path | |
| const ref = makeFunctionReference<"query", any, any>("courses:getBySlug"); | |
| ``` | |
| ### 2. Wrong function path format | |
| ```typescript | |
| // ❌ Wrong: using dot notation | |
| makeFunctionReference("courses.getBySlug") | |
| // ✅ Correct: using colon notation | |
| makeFunctionReference("courses:getBySlug") | |
| ``` | |
| ### 3. Incorrect nested paths | |
| ```typescript | |
| // ❌ Wrong: missing directory separators | |
| makeFunctionReference("integrations:mux:createUpload") | |
| // ✅ Correct: forward slashes for directories, colon for function | |
| makeFunctionReference("integrations/mux:createUpload") | |
| ``` | |
| ### 4. Mixing function types | |
| ```typescript | |
| // ❌ Wrong: mutation is registered as "action" in refs | |
| export const myMutation = mutation({...}); | |
| // In refs: ref("module:myMutation", "action") // WRONG! | |
| // ✅ Correct: type must match the registration | |
| export const myMutation = mutation({...}); | |
| // In refs: ref("module:myMutation", "mutation") // CORRECT | |
| ``` | |
| ## Debugging Tips | |
| ### Runtime Error: "API path expected to be of the form `api.moduleName.functionName`" | |
| This means you're passing an invalid function reference. Check: | |
| 1. The function path format is correct (use colons, not dots) | |
| 2. The path matches the actual file structure | |
| 3. You're using `makeFunctionReference`, not `anyApi` | |
| ### Type Error: "Type instantiation is excessively deep" | |
| This means you're still importing from the generated API: | |
| 1. Search for `from '@/convex/_generated/api'` or `from "./_generated/api"` | |
| 2. Replace with imports from `@/convex/refs/api` or `@/convex/refs/internal` | |
| 3. Make sure helper files don't import `api` or `internal` | |
| ### Function Not Found at Runtime | |
| Check that: | |
| 1. The Convex function is actually exported | |
| 2. The function path in refs matches the file structure | |
| 3. The function type (query/mutation/action) is correct | |
| 4. You've run `bunx convex dev` to sync functions | |
| ## Don't Export `api` or `internal` | |
| **❌ NEVER DO THIS:** | |
| ```typescript | |
| // lib/convex/server.ts | |
| import { api } from "@/convex/_generated/api"; | |
| export { api }; // DON'T re-export! | |
| ``` | |
| This causes conflicts and runtime errors because: | |
| - The generated `api` object is a proxy | |
| - Re-exporting it can cause circular dependencies | |
| - It defeats the purpose of using refs | |
| ## Summary | |
| 1. **Always** use `apiRefs` from `@/convex/refs/api` for public functions | |
| 2. **Always** use `internalRefs` from `@/convex/refs/internal` for internal functions | |
| 3. **Prefer** helper functions over `ctx.run*` when possible | |
| 4. **Never** import `api` or `internal` from `_generated/api` in application code | |
| 5. **Always** use `makeFunctionReference` with explicit paths in refs files | |
| 6. **Match** function types correctly (query/mutation/action) | |
| 7. **Follow** Convex file-based routing for function paths | |
| ## Related Documentation | |
| - [Convex Multiple Repos Pattern](https://docs.convex.dev/production/multiple-repos) | |
| - [Convex Best Practices](https://docs.convex.dev/production/best-practices) | |
| - [makeFunctionReference API](https://docs.convex.dev/generated-api/api) | |
| # Convex API Reference Patterns | |
| ## Overview | |
| This project uses a custom pattern to avoid TypeScript deep instantiation errors (TS2589) when working with Convex's generated API objects. Instead of importing `api` or `internal` from `convex/_generated/api`, we use lightweight function references created with `makeFunctionReference`. | |
| ## The Problem | |
| When the Convex API grows large, TypeScript's type resolver hits its recursion limit when trying to resolve types from the generated `api` and `internal` objects: | |
| ``` | |
| Type instantiation is excessively deep and possibly infinite. TS2589 | |
| ``` | |
| This happens when: | |
| - Using `FunctionReturnType<typeof api.*>` in components | |
| - Passing `api.*` references directly to hooks like `useQuery`, `useMutation`, `useAction` | |
| - Calling Convex functions via `ctx.runQuery`/`ctx.runMutation`/`ctx.runAction` | |
| - Having a large number of Convex functions in your project | |
| ## The Solution | |
| We use `makeFunctionReference` with explicit function paths to create lightweight function references that don't trigger deep type resolution. | |
| ### Directory Structure | |
| ``` | |
| convex/refs/ | |
| ├── api.ts # Public API function references | |
| ├── internal.ts # Internal API function references | |
| └── webhooks.ts # Webhook function references | |
| ``` | |
| ## Usage Patterns | |
| ### 1. Client Components (React Hooks) | |
| **❌ DON'T:** | |
| ```typescript | |
| import { api } from '@/convex/_generated/api'; | |
| import { useQuery, useMutation, useAction } from 'convex/react'; | |
| // Causes deep instantiation errors | |
| const courses = useQuery(api.courses.list); | |
| const createCourse = useMutation(api.courses.create); | |
| const generateDescription = useAction(api.lessons.generateDescription); | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| import { apiRefs } from '@/convex/refs/api'; | |
| import { useQuery, useMutation, useAction } from 'convex/react'; | |
| // Uses lightweight references | |
| const courses = useQuery(apiRefs.courses.list); | |
| const createCourse = useMutation(apiRefs.courses.create); | |
| const generateDescription = useAction(apiRefs.lessons.generateDescription); | |
| ``` | |
| ### 2. Server Components & API Routes | |
| **❌ DON'T:** | |
| ```typescript | |
| import { api } from '@/convex/_generated/api'; | |
| import { createAuthenticatedConvexClient } from '@/lib/convex/server'; | |
| const convex = await createAuthenticatedConvexClient(); | |
| const data = await convex.query(api.courses.getDashboardData, {}); | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| import { apiRefs } from '@/convex/refs/api'; | |
| import { createAuthenticatedConvexClient } from '@/lib/convex/server'; | |
| const convex = await createAuthenticatedConvexClient(); | |
| const data = await convex.query(apiRefs.courses.getDashboardData, {}); | |
| ``` | |
| ### 3. Convex Functions (Actions calling queries/mutations) | |
| **❌ DON'T:** | |
| ```typescript | |
| import { internal } from "./_generated/api"; | |
| import { action } from "./_generated/server"; | |
| export const myAction = action({ | |
| handler: async (ctx, args) => { | |
| // Causes deep instantiation errors | |
| const user = await ctx.runQuery(internal.users.getCurrent); | |
| await ctx.runMutation(internal.messages.create, { text: "Hello" }); | |
| }, | |
| }); | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| import { internalRefs } from "@/convex/refs/internal"; | |
| import { action } from "./_generated/server"; | |
| export const myAction = action({ | |
| handler: async (ctx, args) => { | |
| // Uses lightweight references | |
| const user = await ctx.runQuery(internalRefs.users.getCurrent); | |
| await ctx.runMutation(internalRefs.messages.create, { text: "Hello" }); | |
| }, | |
| }); | |
| ``` | |
| ### 4. Type Annotations | |
| **❌ DON'T:** | |
| ```typescript | |
| import { FunctionReturnType } from "convex/server"; | |
| import { api } from "@/convex/_generated/api"; | |
| // Causes deep instantiation errors | |
| type CourseData = FunctionReturnType<typeof api.courses.getBySlug>; | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| // Define explicit types or use any with eslint-disable | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| type CourseData = any; | |
| // Or use the Convex-generated Doc types | |
| import { Doc } from "@/convex/_generated/dataModel"; | |
| type CourseData = Doc<"courses">; | |
| ``` | |
| ### 5. Preloaded Queries (Next.js) | |
| **❌ DON'T:** | |
| ```typescript | |
| import { api } from '@/convex/_generated/api'; | |
| import { preloadQuery } from 'convex/nextjs'; | |
| const preloaded = await preloadQuery(api.courses.getBySlug, { slug }); | |
| ``` | |
| **✅ DO:** | |
| ```typescript | |
| import { apiRefs } from '@/convex/refs/api'; | |
| import { preloadQuery } from 'convex/nextjs'; | |
| const preloaded = await preloadQuery(apiRefs.courses.getBySlug, { slug }); | |
| ``` | |
| ## Adding New Function References | |
| When you create a new Convex function, add it to the appropriate refs file: | |
| ### For Public Functions (`convex/refs/api.ts`) | |
| ```typescript | |
| export const apiRefs = { | |
| // ... existing refs | |
| myNewModule: { | |
| myQuery: ref("myNewModule:myQuery", "query"), | |
| myMutation: ref("myNewModule:myMutation", "mutation"), | |
| myAction: ref("myNewModule:myAction", "action"), | |
| }, | |
| } as const; | |
| ``` | |
| ### For Internal Functions (`convex/refs/internal.ts`) | |
| ```typescript | |
| export const internalRefs = { | |
| // ... existing refs | |
| myModule: { | |
| internalQuery: ref("model/myModule/internal:internalQuery", "query"), | |
| internalMutation: ref("model/myModule/internal:internalMutation", "mutation"), | |
| }, | |
| } as const; | |
| ``` | |
| ## Function Path Format | |
| Function paths follow Convex's file-based routing convention: | |
| ### Top-level modules | |
| - File: `convex/courses.ts` | |
| - Export: `export const getBySlug = query({...})` | |
| - Path: `"courses:getBySlug"` | |
| ### Nested modules | |
| - File: `convex/integrations/mux.ts` | |
| - Export: `export const createUpload = action({...})` | |
| - Path: `"integrations/mux:createUpload"` | |
| ### Deep nesting | |
| - File: `convex/model/profiles/internal.ts` | |
| - Export: `export const findByEmail = internalQuery({...})` | |
| - Path: `"model/profiles/internal:findByEmail"` | |
| ### Special cases | |
| - Subdirectories with index files: Use the directory name | |
| - Nested actions: Include the full path with forward slashes | |
| ## Helper Function Pattern (PREFERRED) | |
| Instead of calling Convex functions from other Convex functions using `ctx.run*`, prefer using **helper functions** as recommended by Convex best practices: | |
| **❌ AVOID:** | |
| ```typescript | |
| // convex/users.ts | |
| export const getCurrentUser = internalQuery({ | |
| handler: async (ctx) => { | |
| const identity = await ctx.auth.getUserIdentity(); | |
| return await ctx.db.query("users").withIndex("by_clerk_id", ...).unique(); | |
| }, | |
| }); | |
| // convex/messages.ts | |
| export const sendMessage = mutation({ | |
| handler: async (ctx, args) => { | |
| // Requires internal ref and separate transaction | |
| const user = await ctx.runQuery(internalRefs.users.getCurrentUser); | |
| await ctx.db.insert("messages", { userId: user._id, ...args }); | |
| }, | |
| }); | |
| ``` | |
| **✅ PREFER:** | |
| ```typescript | |
| // convex/helpers/users.ts (NOT registered as a Convex function) | |
| import { QueryCtx } from "../_generated/server"; | |
| export async function getCurrentUser(ctx: QueryCtx) { | |
| const identity = await ctx.auth.getUserIdentity(); | |
| return await ctx.db.query("users").withIndex("by_clerk_id", ...).unique(); | |
| } | |
| // convex/messages.ts | |
| import { getCurrentUser } from "./helpers/users"; | |
| export const sendMessage = mutation({ | |
| handler: async (ctx, args) => { | |
| // Same transaction, no ref needed, cleaner code | |
| const user = await getCurrentUser(ctx); | |
| await ctx.db.insert("messages", { userId: user._id, ...args }); | |
| }, | |
| }); | |
| ``` | |
| **Benefits of helper functions:** | |
| - Execute in the same transaction | |
| - No need for function references | |
| - Reduced TypeScript complexity | |
| - Better performance (no extra function calls) | |
| - Easier to refactor | |
| **When to use `ctx.run*` with refs:** | |
| - Calling functions from actions (different runtime) | |
| - Need partial rollback (catch errors in `ctx.runMutation`) | |
| - Using Convex Components | |
| - Scheduling functions with `ctx.scheduler` | |
| ## Common Pitfalls | |
| ### 1. Using `anyApi` without explicit paths | |
| ```typescript | |
| // ❌ This doesn't work at runtime | |
| const ref = anyApi as FunctionReference<"query", "public", any, any>; | |
| // ✅ Must use makeFunctionReference with explicit path | |
| const ref = makeFunctionReference<"query", any, any>("courses:getBySlug"); | |
| ``` | |
| ### 2. Wrong function path format | |
| ```typescript | |
| // ❌ Wrong: using dot notation | |
| makeFunctionReference("courses.getBySlug") | |
| // ✅ Correct: using colon notation | |
| makeFunctionReference("courses:getBySlug") | |
| ``` | |
| ### 3. Incorrect nested paths | |
| ```typescript | |
| // ❌ Wrong: missing directory separators | |
| makeFunctionReference("integrations:mux:createUpload") | |
| // ✅ Correct: forward slashes for directories, colon for function | |
| makeFunctionReference("integrations/mux:createUpload") | |
| ``` | |
| ### 4. Mixing function types | |
| ```typescript | |
| // ❌ Wrong: mutation is registered as "action" in refs | |
| export const myMutation = mutation({...}); | |
| // In refs: ref("module:myMutation", "action") // WRONG! | |
| // ✅ Correct: type must match the registration | |
| export const myMutation = mutation({...}); | |
| // In refs: ref("module:myMutation", "mutation") // CORRECT | |
| ``` | |
| ## Debugging Tips | |
| ### Runtime Error: "API path expected to be of the form `api.moduleName.functionName`" | |
| This means you're passing an invalid function reference. Check: | |
| 1. The function path format is correct (use colons, not dots) | |
| 2. The path matches the actual file structure | |
| 3. You're using `makeFunctionReference`, not `anyApi` | |
| ### Type Error: "Type instantiation is excessively deep" | |
| This means you're still importing from the generated API: | |
| 1. Search for `from '@/convex/_generated/api'` or `from "./_generated/api"` | |
| 2. Replace with imports from `@/convex/refs/api` or `@/convex/refs/internal` | |
| 3. Make sure helper files don't import `api` or `internal` | |
| ### Function Not Found at Runtime | |
| Check that: | |
| 1. The Convex function is actually exported | |
| 2. The function path in refs matches the file structure | |
| 3. The function type (query/mutation/action) is correct | |
| 4. You've run `bunx convex dev` to sync functions | |
| ## Don't Export `api` or `internal` | |
| **❌ NEVER DO THIS:** | |
| ```typescript | |
| // lib/convex/server.ts | |
| import { api } from "@/convex/_generated/api"; | |
| export { api }; // DON'T re-export! | |
| ``` | |
| This causes conflicts and runtime errors because: | |
| - The generated `api` object is a proxy | |
| - Re-exporting it can cause circular dependencies | |
| - It defeats the purpose of using refs | |
| ## Summary | |
| 1. **Always** use `apiRefs` from `@/convex/refs/api` for public functions | |
| 2. **Always** use `internalRefs` from `@/convex/refs/internal` for internal functions | |
| 3. **Prefer** helper functions over `ctx.run*` when possible | |
| 4. **Never** import `api` or `internal` from `_generated/api` in application code | |
| 5. **Always** use `makeFunctionReference` with explicit paths in refs files | |
| 6. **Match** function types correctly (query/mutation/action) | |
| 7. **Follow** Convex file-based routing for function paths |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment