### Run local Convex examples Source: https://github.com/get-convex/convex-helpers/blob/main/README.md Install dependencies and run the development server to test local examples. This command also sets up a symlink for convex-helpers for live editing. ```sh npm i && npm run dev ``` -------------------------------- ### Install Development Dependencies Source: https://github.com/get-convex/convex-helpers/blob/main/CONTRIBUTING.md Run this command in the root of the repository to install all necessary dependencies for development. Do not run this command in the `packages/convex-helpers` directory. ```sh npm install ``` -------------------------------- ### startMigration Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-migrations.md Starts or resumes a specific migration process. It can be configured with a starting cursor and batch size for controlled execution. ```APIDOC ## startMigration ### Description Starts or resumes a migration. Allows specifying a starting point and the number of documents to process per batch. ### Signature ```typescript export async function startMigration( ctx: GenericMutationCtx, migration: FunctionReference<"mutation">, options?: { startCursor?: string; batchSize?: number; } ): Promise ``` ### Parameters * **ctx** (`GenericMutationCtx`) - Required. Mutation context. * **migration** (`FunctionReference`) - Required. Migration function to run. * **options.startCursor** (`string`) - Optional. Override cursor to resume from. * **options.batchSize** (`number`) - Optional. Defaults to 1000. Documents per batch. ### Returns A `Promise` resolving to `MigrationStatus` with progress information. ### Example ```typescript const status = await startMigration( ctx, internal.migrations.updateSchema, { batchSize: 500 } ); console.log(`Processed ${status.count} documents`); ``` ``` -------------------------------- ### Install convex-helpers npm package Source: https://github.com/get-convex/convex-helpers/blob/main/README.md Install the latest version of the convex-helpers npm package using npm. ```sh npm install convex-helpers@latest ``` -------------------------------- ### Example Action in Convex Source: https://github.com/get-convex/convex-helpers/blob/main/convex/_generated/ai/guidelines.md This is an example of the syntax for an action in Convex. Actions can run in the Node.js runtime and do not have access to the database. ```typescript import { action } from "./_generated/server"; export const exampleAction = action({ args: {}, handler: async (ctx, args) => { console.log("This action does not return anything"); return null; }, }); ``` -------------------------------- ### Row-Level Security Setup Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Implements row-level security for Convex functions using `customQuery` and `customMutation`. This example defines rules for user access based on authentication status and user properties, and configures default policies. ```typescript import { customCtx, customMutation, customQuery, } from "convex-helpers/server/customFunctions"; import { Rules, RLSConfig, wrapDatabaseReader, wrapDatabaseWriter, } from "convex-helpers/server/rowLevelSecurity"; import { DataModel } from "./_generated/dataModel"; import { mutation, query, QueryCtx } from "./_generated/server"; async function rlsRules(ctx: QueryCtx) { const identity = await ctx.auth.getUserIdentity(); return { users: { read: async (_, user) => { // Unauthenticated users can only read users over 18 if (!identity && user.age < 18) return false; return true; }, insert: async (_, user) => { return true; }, modify: async (_, user) => { if (!identity) throw new Error("Must be authenticated to modify a user"); // Users can only modify their own user return user.tokenIdentifier === identity.tokenIdentifier; }, }, } satisfies Rules; } // By default, tables with no rule have `defaultPolicy` set to "allow". const config: RLSConfig = { defaultPolicy: "deny" }; const queryWithRLS = customQuery( query, customCtx(async (ctx) => ({ db: wrapDatabaseReader(ctx, ctx.db, await rlsRules(ctx), config), })), ); const mutationWithRLS = customMutation( mutation, customCtx(async (ctx) => ({ db: wrapDatabaseWriter(ctx, ctx.db, await rlsRules(ctx), config), })), ); ``` -------------------------------- ### Fetch Page Starting at a Given Index Key (Descending) Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Retrieves messages starting from a specific index key, ordered in descending order (recent first). The example fetches messages from the last 24 hours. ```javascript const { page, indexKeys, hasMore } = await getPage(ctx, { table: "messages", startIndexKey: [Date.now() - 24 * 60 * 60 * 1000], startInclusive: true, order: "desc", }); ``` -------------------------------- ### Registering and Using Triggers Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-patterns.md Example demonstrating how to instantiate the Triggers class, register trigger functions for different tables, and wrap the mutation builder to enable triggers. ```APIDOC ## Example Usage ```typescript import { Triggers } from "convex-helpers/server/triggers"; import { mutation as rawMutation } from "./_generated/server"; import { customMutation, customCtx } from "convex-helpers/server/customFunctions"; const triggers = new Triggers(); // Register triggers for different tables triggers.register("users", async (ctx, change) => { if (change.operation === "insert") { console.log("New user:", change.newDoc.email); // Could send welcome email, create profile, etc. } }); triggers.register("posts", async (ctx, change) => { if (change.operation === "update") { const { oldDoc, newDoc } = change; if (oldDoc.published !== newDoc.published && newDoc.published) { console.log("Post published:", newDoc.title); // Broadcast notification, update search index, etc. } } }); // Wrap the mutation builder to apply triggers export const mutation = customMutation( rawMutation, customCtx(triggers.wrapDB) ); ``` ``` -------------------------------- ### Server Table & Configuration Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/README.md Utilities for defining tables, handling environment variables, and getting deployment information. ```APIDOC ## Server Table & Configuration ### Description Utilities for defining tables, handling environment variables, and getting deployment information. ### Functions - `Table(schema)`: Define a table with system fields and validators. - `missingEnvVariableError(name)`: Format error messages for missing environment variables. - `deploymentName()`: Get the Convex deployment name. ``` -------------------------------- ### Start a Migration Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-migrations.md Initiate or resume a migration process using `startMigration`. You can specify options like `startCursor` to resume from a specific point or `batchSize` to control the number of documents processed per batch. The default batch size is 1000. ```typescript const status = await startMigration( ctx, internal.migrations.updateSchema, { batchSize: 500 } ); console.log(`Processed ${status.count} documents`); ``` -------------------------------- ### Create `queryWithSession` Helper Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md This example shows how to create a custom query function (`queryWithSession`) using `customQuery` and `SessionIdArg`. It integrates session lookup logic. ```typescript import { customQuery } from "convex-helpers/server/customFunctions"; import { SessionIdArg } from "convex-helpers/server/sessions"; export const queryWithSession = customQuery(query, { args: SessionIdArg, input: async (ctx, { sessionId }) => { const anonymousUser = await getAnonUser(ctx, sessionId); return { ctx: { ...ctx, anonymousUser }, args: {} }; }, }); ``` -------------------------------- ### Migrations from convex-helpers/server/migrations Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Import functions for managing database migrations. This includes starting migrations and running them serially. ```typescript import { makeMigration, startMigration, startMigrationsSerially } from "convex-helpers/server/migrations"; ``` -------------------------------- ### Define HTTP Endpoints with Hono Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Use Hono to define HTTP API endpoints within your Convex project. This example shows basic setup and a GET request handler. ```typescript import { Hono } from "hono"; import { HonoWithConvex, HttpRouterWithHono } from "convex-helpers/server/hono"; import { ActionCtx } from "./_generated/server"; const app: HonoWithConvex = new Hono(); // See the [guide on Stack](https://stack.convex.dev/hono-with-convex) // for tips on using Hono for HTTP endpoints. app.get("/", async (c) => { return c.json("Hello world!"); }); export default new HttpRouterWithHono(app); ``` -------------------------------- ### Example Usage of Deprecated crud Function Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-table.md This example demonstrates how to use the deprecated `crud` function to set up CRUD operations for a 'users' table. It requires importing `crud` from `convex-helpers/server` and internal query/mutation functions. ```typescript import { crud } from "convex-helpers/server"; import { internalQuery, internalMutation } from "./_generated/server"; const Users = Table("users", { name: v.string(), }); export const { create, read, update, destroy, paginate } = crud( Users, internalQuery, internalMutation ); ``` -------------------------------- ### Database Triggers Setup Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Set up database triggers for real-time updates or event handling using the `Triggers` class. Register specific operations like 'insert' to execute custom logic. ```typescript import { Triggers } from "convex-helpers/server/triggers"; const triggers = new Triggers(); triggers.register("messages", async (ctx, change) => { if (change.operation === "insert") { // Update conversation's lastMessageAt } }); export const mutation = customMutation(rawMutation, customCtx(triggers.wrapDB)); ``` -------------------------------- ### useQuery Hook Example Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/react-hooks.md Demonstrates how to use the `useQuery` hook to fetch data from a Convex query and handle different loading states (pending, error, success). It shows how to access the data or error based on the query's status. ```typescript import { useQuery } from "convex-helpers/react"; import { api } from "./_generated/api"; export function UserList() { const result = useQuery(api.users.getList, { role: "admin" }); if (result.status === "pending") { return
Loading...
; } if (result.status === "error") { return
Error: {result.error.message}
; } return (
{result.data.map(user => (
{user.name}
))}
); } ``` ```typescript // Or use the boolean flags: export function UserProfile() { const result = useQuery(api.users.getOne, { id: userId }); if (result.isPending) return ; if (result.isError) return ; return ; } ``` ```typescript // Skip the query conditionally: export function ConditionalQuery() { const [id, setId] = useState(null); const result = useQuery(api.users.getOne, id ? { id } : "skip"); return result.status === "pending" ? null : result.data; } ``` -------------------------------- ### Example Trigger Implementation Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-patterns.md Illustrates a practical example of a trigger function that updates a conversation's last message timestamp or decrements a message count upon message insertion or deletion. ```APIDOC **Example Trigger:** ```typescript triggers.register("messages", async (ctx, change) => { const { operation, id, oldDoc, newDoc } = change; if (operation === "insert") { // New message inserted const message = newDoc; // Update conversation's lastMessageAt const conversation = await ctx.innerDb.get(message.conversationId); await ctx.innerDb.patch(message.conversationId, { lastMessageAt: message._creationTime, }); } if (operation === "delete") { // Message deleted const message = oldDoc; // Decrement message count const conversation = await ctx.innerDb.get(message.conversationId); await ctx.innerDb.patch(message.conversationId, { messageCount: conversation.messageCount - 1, }); } }); ``` ``` -------------------------------- ### Example Trigger for Message Operations Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-patterns.md A trigger example demonstrating how to handle 'insert' and 'delete' operations on a 'messages' table. It updates a conversation's `lastMessageAt` on insert and decrements `messageCount` on delete. ```typescript triggers.register("messages", async (ctx, change) => { const { operation, id, oldDoc, newDoc } = change; if (operation === "insert") { // New message inserted const message = newDoc; // Update conversation's lastMessageAt const conversation = await ctx.innerDb.get(message.conversationId); await ctx.innerDb.patch(message.conversationId, { lastMessageAt: message._creationTime, }); } if (operation === "delete") { // Message deleted const message = oldDoc; // Decrement message count const conversation = await ctx.innerDb.get(message.conversationId); await ctx.innerDb.patch(message.conversationId, { messageCount: conversation.messageCount - 1, }); } }); ``` -------------------------------- ### Track and Get Session or Create Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-sessions.md Tracks a user session by ID, creating it if it doesn't exist. Used for initializing session data. ```typescript // convex/sessions.ts import { getSessionOrCreate, } from "convex-helpers/server/sessions"; export const trackSession = mutation({ args: { sessionId: v.string() }, handler: async (ctx, { sessionId }) => { const session = await getSessionOrCreate(ctx, "sessions", sessionId); return session._id; }, }); export const update = mutation({ args: { sessionId: v.string(), userId: v.optional(v.id("users")), }, handler: async (ctx, { sessionId, userId }) => { const session = await getSessionOrCreate(ctx, "sessions", sessionId); if (userId) { await ctx.db.patch(session._id, { userId }); } return session; }, }); ``` -------------------------------- ### Idempotent Migration Example Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-migrations.md Ensures a migration can be run multiple times without unintended side effects by checking for existing conditions before applying changes. ```typescript export const safe = migration({ table: "users", migrateOne: async (ctx, doc) => { if (!doc.hasField) { // Check before applying await ctx.db.patch(doc._id, { newField: value }); } }, }); ``` -------------------------------- ### Example Schema Definition Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-crud.md Define your table schema using `defineTable` and `v` validators from `convex/server` and `convex/values`. Ensure all fields are included with their validators and import the schema as `SchemaDefinition`. ```typescript // 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.optional(v.string()), }), }); ``` -------------------------------- ### missingEnvVariableError Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-table.md Creates a formatted error message to guide users on setting a missing environment variable. ```APIDOC ## missingEnvVariableError ### Description Creates a formatted error message with instructions for setting a missing environment variable. ### Signature ```typescript export function missingEnvVariableError( envVarName: string, whereToGet: string, ): string ``` ### Parameters #### Path Parameters - **envVarName** (string) - Required - The missing environment variable name (e.g., "OPENAI_API_KEY"). - **whereToGet** (string) - Required - URL or description of where to obtain the variable. ### Returns `string` - Formatted error message with setup instructions. ### Example ```typescript import { missingEnvVariableError } from "convex-helpers/server"; const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) { throw new Error( missingEnvVariableError( "OPENAI_API_KEY", "https://platform.openai.com/account/api-keys" ) ); } ``` ``` -------------------------------- ### Get or Create Session Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-sessions.md Use `getSessionOrCreate` to retrieve an existing session document or create a new one if it doesn't exist. This is useful for initializing a user's session on the server. ```typescript export const startSession = mutation({ args: { sessionId: v.string() }, handler: async (ctx, args) => { const session = await getSessionOrCreate( ctx, "sessions", args.sessionId ); return { sessionId: session._id }; }, }); ``` -------------------------------- ### Registering Triggers for Data Changes Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Example of registering multiple triggers for 'users' table changes. Handles computed fields, denormalized counts, external service calls (Clerk), and cascading deletes. ```typescript import { mutation as rawMutation, } from "./_generated/server"; import { DataModel } from "./_generated/dataModel"; import { Triggers } from "convex-helpers/server/triggers"; import { customCtx, customMutation, } from "convex-helpers/server/customFunctions"; const triggers = new Triggers(); // 1. Attach a computed `fullName` field to every user. triggers.register("users", async (ctx, change) => { if (change.newDoc) { const fullName = `${change.newDoc.firstName} ${change.newDoc.lastName}`; // Abort the mutation if document is invalid. if (fullName === "The Balrog") { throw new Error("you shall not pass"); } // Update denormalized field. Check first to avoid recursion if (change.newDoc.fullName !== fullName) { await ctx.db.patch(change.id, { fullName }); } } }); // 2. Keep a denormalized count of all users. triggers.register("users", async (ctx, change) => { // Note writing the count to a single document increases write contention. // There are more scalable methods if you need high write throughput. const countDoc = (await ctx.db.query("userCount").unique())!; if (change.operation === "insert") { await ctx.db.patch(countDoc._id, { count: countDoc.count + 1 }); } else if (change.operation === "delete") { await ctx.db.patch(countDoc._id, { count: countDoc.count - 1 }); } }); // 3. After the mutation, send the new user info to Clerk. // Even if a user is modified multiple times in a single mutation, // `internal.users.updateClerkUser` runs once. const scheduled: Record, Id<"_scheduled_functions">> = {}; triggers.register("users", async (ctx, change) => { if (scheduled[change.id]) { await ctx.scheduler.cancel(scheduled[change.id]); } scheduled[change.id] = await ctx.scheduler.runAfter( 0, internal.users.updateClerkUser, { user: change.newDoc }, ); }); // 4. When a user is deleted, delete their messages (cascading deletes). triggers.register("users", async (ctx, change) => { // Using relationships.ts helpers for succinctness. await asyncMap( await getManyFrom(ctx.db, "messages", "owner", change.id), (message) => ctx.db.delete(message._id), ); }); // Use `mutation` to define all mutations, and the triggers will get called. export const mutation = customMutation(rawMutation, customCtx(triggers.wrapDB)); ``` -------------------------------- ### Paginate Messages by Channel with Streams Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Use `stream` to paginate messages across channels a user is a member of. This example demonstrates joining message data with channel data for ordered retrieval. ```typescript import { stream } from "convex-helpers/server/stream"; import schema from "./schema"; // schema has: // channelMemberships: defineTable(...).index("userId", ["userId", "channelId"]) // channels: defineTable(...) // messages: defineTable(...).index("channelId", ["channelId"]) // Return a paginated stream of { ...channel, ...message } // ordered by // [channelMembership.channelId, channelMembership._creationTime, message.channelId, message._creationTime], // i.e. ordered by [channel._id, message._creationTime] // if we assume the channelMemberships.userId index is unique export const latestMessages = query({ args: { paginationOpts: paginationOptsValidator }, handler: async (ctx, { paginationOpts }) => { // Get the channels the user is a member of const channelMemberships = stream(ctx.db, schema) .query("channelMemberships") .withIndex("userId", q => q.eq("userId", await getAuthedUserId(ctx))); // Map membership to the channel info (including channel name, etc.) const channels = channelMemberships.map(async (membership) => { return (await ctx.db.get(membership.channelId))!; }); // For each channel, expand it into the messages in that channel, // with the channel's fields also included. const messages = channels.flatMap(async (channel) => stream(ctx.db, stream) .query("messages") .withIndex("channelId", q => q.eq("channelId", channel._id)) .map(async (message) => { ...channel, ...message }), ["channelId", "_creationTime"] ); return await messages.paginate(paginationOpts); }, }); ``` -------------------------------- ### Implement CORS with corsHttpRouter Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Integrate CORS support into your Convex httpAction routes by wrapping your existing `httpRouter` with `corsRouter`. This setup handles OPTIONS preflight requests and allows for detailed configuration of CORS behavior. ```typescript import { corsRouter } from "convex-helpers/server/cors"; import { httpRouter } from "convex/server"; import { httpAction } from "./_generated/api"; // Your standard Convex http router: const http = httpRouter(); // Your CORS router: const cors = corsRouter( http, // Optional configuration, can be omitted entirely { // allowedOrigins can also be a function // allowedOrigins: (req: Request) => Promise allowedOrigins: ["http://localhost:8080"], // Default: ["*"] allowedMethods: ["GET", "POST"], // Defaults to route spec method allowedHeaders: ["Content-Type"], // Default: ["Content-Type"] exposedHeaders: ["Custom-Header"], // Default: ["Content-Range", "Accept-Ranges"] allowCredentials: true, // Default: false browserCacheMaxAge: 60, // Default: 86400 (1 day) // returns a 403 if the origin is not allowed enforceAllowOrigins: true, // Default: false debug: true, // Default: false }, ); cors.route({ path: "/foo", method: "GET", handler: httpAction(async () => { return new Response("ok"); }), }); cors.route({ path: "/foo", // You can register multiple methods for the same path method: "POST", handler: httpAction(async () => { return new Response("ok"); }), // You can provide configuration per route allowedOrigins: ["http://localhost:8080"], }); // Non-CORS routes still work, provided they're on different paths. http.route({ path: "/notcors", method: "GET", handler: httpAction(async () => { return new Response("ok"); }), }); // Export http (or cors.http) export default http; ``` -------------------------------- ### Running Database Migrations Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Create and run database migrations using `makeMigration` and `startMigration`. Define migration logic for specific tables and documents. ```typescript import { makeMigration, startMigration } from "convex-helpers/server/migrations"; const migration = makeMigration(internalMutation, { migrationTable: "migrations" }); export const addFields = migration({ table: "users", migrateOne: async (ctx, doc) => { if (!doc.newField) { await ctx.db.patch(doc._id, { newField: defaultValue }); } }, }); // Run with: npx convex run addFields '{fn: "migrations:addFields"}' ``` -------------------------------- ### Run a single migration programmatically Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-migrations.md Initiate a migration process from your code using `startMigration`. This function takes the Convex context, the migration function, and optional configuration like `batchSize`. ```typescript import { startMigration } from "convex-helpers/server/migrations"; export const runMigration = internalMutation(async (ctx) => { const result = await startMigration( ctx, internal.migrations.renameUserName, { batchSize: 100, } ); return result; }); ``` -------------------------------- ### Run Database Migrations with Server Helpers Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/README.md Leverage `makeMigration` and `startMigration` from `convex-helpers/server/migrations` to define and execute database schema migrations. This pattern allows for incremental updates to your data structure. ```typescript import { makeMigration, startMigration } from "convex-helpers/server/migrations"; const migration = makeMigration(internalMutation, { migrationTable: "migrations" }); export const addFields = migration({ table: "users", migrateOne: async (ctx, doc) => { if (!doc.newField) { await ctx.db.patch(doc._id, { newField: value }); } }, }); ``` -------------------------------- ### startMigrationsSerially Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-migrations.md Executes multiple migrations sequentially, ensuring that only completed migrations are skipped. Useful for orchestrating a series of updates. ```APIDOC ## startMigrationsSerially ### Description Runs multiple migrations in sequence, skipping any that have already been completed. This is useful for applying a series of updates in a controlled manner. ### Signature ```typescript export async function startMigrationsSerially( ctx: GenericMutationCtx, migrations: FunctionReference<"mutation">[] ): Promise ``` ### Parameters * **ctx** (`GenericMutationCtx`) - Required. Mutation context. * **migrations** (`FunctionReference[]`) - Required. An array of migration functions to execute. ### Returns A `Promise` that resolves when all specified migrations have completed. ### Example ```typescript export const initializeMigrations = internalMutation(async (ctx) => { await startMigrationsSerially(ctx, [ internal.migrations.v1AddFields, internal.migrations.v2RenameTables, internal.migrations.v3Backfill, ]); }); // Run with: npx convex run initializeMigrations ``` ``` -------------------------------- ### Convex Helpers Entry Points Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/MANIFEST.md Lists the various entry points available for importing functionalities from the convex-helpers library. ```text convex-helpers convex-helpers/server convex-helpers/server/crud convex-helpers/server/relationships convex-helpers/server/retries convex-helpers/server/triggers convex-helpers/server/migrations convex-helpers/server/sessions convex-helpers/server/hono convex-helpers/server/rowLevelSecurity convex-helpers/server/filter convex-helpers/server/pagination convex-helpers/server/stream convex-helpers/react convex-helpers/react/cache convex-helpers/validators ``` -------------------------------- ### Get Messages for a Session Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-sessions.md Retrieves the latest messages for a given session, ordered by timestamp in descending order. ```typescript // convex/messages.ts export const getForSession = query({ args: { sessionId: v.string() }, handler: async (ctx, { sessionId }) => { return await ctx.db .query("messages") .withIndex("by_sessionId", q => q.eq("sessionId", sessionId)) .order("desc") .take(50); }, }); ``` -------------------------------- ### Setting up ConvexQueryCacheProvider Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Wrap your application with ConvexQueryCacheProvider to enable query caching. This provider should be placed inside ConvexProvider. Optional props include expiration, maxIdleEntries, and debug. ```tsx import { ConvexQueryCacheProvider } from "convex-helpers/react/cache"; // For Next.js, import from "convex-helpers/react/cache/provider"; instead export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return ( {children} ); } ``` -------------------------------- ### Extract Object Type Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/types.md Use `ObjectType` to get the type of fields defined in a Convex object validator. Import `ObjectType` from `convex/values`. ```typescript import { ObjectType } from "convex/values"; import { v } from "convex/values"; const fields = { name: v.string(), email: v.string(), }; type Fields = ObjectType; // Fields = { name: string; email: string } ``` -------------------------------- ### Generate Open API Spec Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Run this command in your Convex folder to generate an Open API specification file. Useful for creating clients in unsupported languages or connecting with tools like Retool. Connects to dev deployment by default. ```bash npx convex-helpers open-api-spec ``` -------------------------------- ### Track Session Mutation Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-sessions.md Use this mutation on the server to get or create a session based on a client-provided ID. It requires the 'sessions' table to exist. ```typescript import { getSessionOrCreate, cleanupSessions } from "convex-helpers/server/sessions"; export const trackSession = mutation({ args: { sessionId: v.string() }, handler: async (ctx, { sessionId }) => { const session = await getSessionOrCreate(ctx, "sessions", sessionId); return session; }, }); export const cleanup = mutation(async (ctx) => { const cleaned = await cleanupSessions(ctx, "sessions"); return { deletedCount: cleaned }; }); ``` -------------------------------- ### Document Type by Name Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/types.md Use `DocumentByName` to get the type of a document from your `DataModel` based on its table name. Requires importing `DataModel` from your generated types. ```typescript import { DocumentByName } from "convex/server"; import { DataModel } from "./_generated/dataModel"; type User = DocumentByName; // User = { _id: Id<"users">; _creationTime: number; name: string; ... } ``` -------------------------------- ### Setting Up CRUD Operations Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Use the `crud` function to quickly set up standard Create, Read, Update, and Delete operations for a given schema and table. ```typescript import { crud } from "convex-helpers/server/crud"; import schema from "./schema"; export const { create, read, update, destroy, paginate } = crud(schema, "users"); ``` -------------------------------- ### Rate Limiting from convex-helpers/server/rateLimit Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Import the defineRateLimits function for setting up rate limiting. This helps protect your Convex application from abuse. ```typescript import { defineRateLimits } from "convex-helpers/server/rateLimit"; ``` -------------------------------- ### Schedule Cron Job with Interval Source: https://github.com/get-convex/convex-helpers/blob/main/convex/_generated/ai/guidelines.md Define cron jobs using `cronJobs` and schedule them with `crons.interval`. This example runs an internal action every two hours. ```typescript import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; import { internalAction } from "./_generated/server"; const empty = internalAction({ args: {}, handler: async (ctx, args) => { console.log("empty"); }, }); const crons = cronJobs(); // Run `internal.crons.empty` every two hours. crons.interval("delete inactive users", { hours: 2 }, internal.crons.empty, {}); export default crons; ``` -------------------------------- ### Import Core Utilities from Convex Helpers Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/README.md Import core utilities from the main 'convex-helpers' package. This is the general entry point for many helper functions. ```typescript import { ... } from "convex-helpers" ``` -------------------------------- ### Import Server Utilities from Convex Helpers Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/README.md Import server-specific utilities from the 'convex-helpers/server' module. Use this for backend operations. ```typescript import { ... } from "convex-helpers/server" ``` -------------------------------- ### PaginationOptions Type Definition Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/types.md Defines the structure for configuring paginated queries. Use `numItems` to specify the page size and `cursor` for the starting point of the next page. ```typescript type PaginationOptions = { numItems: number; cursor: string | null; endCursor?: string; }; ``` -------------------------------- ### Fetch First Page by Creation Time Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Retrieves the first page of documents from a table, ordered by creation time. Uses the `getPage` helper. ```javascript const { page, indexKeys, hasMore } = await getPage(ctx, { table: "messages", }); ``` -------------------------------- ### Read from Database with GenericDatabaseReader Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/types.md Utilize `GenericDatabaseReader` for read-only database operations within Convex queries. This interface provides methods like `get` for retrieving documents by ID. ```typescript import { GenericDatabaseReader } from "convex/server"; async function readFromDB( db: GenericDatabaseReader, id: GenericId<"users"> ) { return await db.get(id); } ``` -------------------------------- ### Run multiple migrations in series Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-migrations.md Execute a list of migrations sequentially using `startMigrationsSerially`. This ensures that one migration completes before the next one begins. ```typescript import { startMigrationsSerially } from "convex-helpers/server/migrations"; export const runAllMigrations = internalMutation(async (ctx) => { await startMigrationsSerially(ctx, [ internal.migrations.renameUserName, internal.migrations.addUserTimestamps, ]); }); ``` -------------------------------- ### Get or Create Session in Server Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/README.md Use `getSessionOrCreate` from `convex-helpers/server/sessions` to retrieve an existing session or create a new one if it doesn't exist. This is essential for managing user sessions on the server. ```typescript import { getSessionOrCreate } from "convex-helpers/server/sessions"; const session = await getSessionOrCreate(ctx, "sessions", sessionId); ``` -------------------------------- ### Testing Migrations with Dry-Run Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-migrations.md Verifies migration logic before applying it to production data by using a dry-run mode. Recommended for initial testing. ```typescript const status = await startMigration(ctx, internal.migrations.test, { batchSize: 10, // Small batch for testing }); ``` -------------------------------- ### Server Utilities from convex-helpers/server Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Import server-specific utilities for managing Convex applications. This includes table definitions, CRUD operations, and environment variable handling. ```typescript // Server utilities import { Table, crud, missingEnvVariableError, deploymentName } from "convex-helpers/server"; ``` -------------------------------- ### Server Migrations Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/README.md Tools for defining and running database migrations. ```APIDOC ## Server Migrations ### Description Tools for defining and running database migrations. ### Functions - `makeMigration(definition)`: Define a batch migration. - `startMigration(migration)`: Run a single migration. - `startMigrationsSerially(migrations)`: Run multiple migrations in sequence. ``` -------------------------------- ### Full-Text Search Query in Convex Source: https://github.com/get-convex/convex-helpers/blob/main/convex/_generated/ai/guidelines.md Example of performing a full-text search on the 'body' field of 'messages' documents, filtered by 'channel', and limiting the results. This utilizes the `withSearchIndex` method for efficient searching. ```typescript const messages = await ctx.db .query("messages") .withSearchIndex("search_body", (q) => q.search("body", "hello hi").eq("channel", "#general"), ) .take(10); ``` -------------------------------- ### Get Document or Throw Error Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-relationships.md Use `getOrThrow` to fetch a single document by its ID. It throws an error if the document is not found. You can provide the table name explicitly or let it be inferred from the ID. ```typescript import { getOrThrow } from "convex-helpers/server/relationships"; const user = await getOrThrow(ctx, "users", userId); const comment = await getOrThrow(ctx, commentId); ``` -------------------------------- ### Get Document or Throw Error in Server Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/README.md Employ `getOrThrow` from `convex-helpers/server/relationships` to retrieve a specific document by ID, throwing an error if the document is not found. This is useful for ensuring required data exists before proceeding. ```typescript import { getOrThrow } from "convex-helpers/server/relationships"; const user = await getOrThrow(ctx, "users", userId); ``` -------------------------------- ### Initialize ConvexQueryCacheProvider Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/react-cache.md Wrap your application with ConvexQueryCacheProvider to enable query result caching across the component tree. This provider should be nested within ConvexProvider. ```typescript import { ConvexQueryCacheProvider } from "convex-helpers/react/cache/provider"; import { ConvexProvider, useQuery } from "convex/react"; import { ConvexClient } from "convex/react"; const client = new ConvexClient(process.env.REACT_APP_CONVEX_URL!); function App() { return ( ); } ``` -------------------------------- ### Fetch Next Page with getPage Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Fetches the subsequent page of documents by providing the last index key from the previous page. Uses the `getPage` helper. ```javascript const { page: page2, indexKeys: indexKeys2, hasMore: hasMore2, } = await getPage(ctx, { table: "messages", startIndexKey: indexKeys[indexKeys.length - 1], }); ``` -------------------------------- ### Get Multiple Documents or Null Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-relationships.md Use `getAll` to fetch multiple documents by their IDs. It returns an array where missing documents are represented by `null`. Provide the table name explicitly or infer it from the IDs. ```typescript import { getAll } from "convex-helpers/server/relationships"; const userIds = [userId1, userId2, userId3]; const users = await getAll(ctx.db, "users", userIds); // [user1, null, user3] if user2 was deleted const comments = await getAll(ctx.db, commentIds); ``` -------------------------------- ### Validate Table ID Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Demonstrates validating a value as a table ID. The first example validates only that it's a string, while the second, using the `db` context, validates that it's a valid ID for the specified table. ```typescript // Warning: this only validates that `accountId` is a string. validate(vv.id("accounts"), accountId); // Whereas this validates that `accountId` is an id for the accounts table. validate(vv.id("accounts"), accountId, { db: ctx.db }); ``` -------------------------------- ### Define Schema with Validators Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Defines a Convex database schema using various validators including literals, nullable, deprecated, and branded strings. This example shows how to integrate custom validators into your schema definition. ```typescript // convex/schema.ts // ... other imports export default defineSchema({ accounts: defineTable({ balance: nullable(v.bigint()), status: literals("active", "inactive"), email: emailValidator, oldField: deprecated, }).index("status", ["status"]), //... }); ``` -------------------------------- ### Pagination from convex-helpers/server/pagination Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Import the getPage function for handling paginated data retrieval. This is a core utility for managing large result sets. ```typescript import { getPage } from "convex-helpers/server/pagination"; ``` -------------------------------- ### Paginator with Index, Order, and Author Filter Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Shows how to use the `paginator` function to query messages by a specific author, ordered by index in descending order, and paginated using provided options. Requires schema and paginationOptsValidator. ```typescript import { paginator } from "convex-helpers/server/pagination"; import schema from "./schema"; export const list = query({ args: { opts: paginationOptsValidator, author: v.id("users") }, handler: async (ctx, { opts, author }) => { return await paginator(ctx.db, schema) .query("messages") .withIndex("by_author", (q) => q.eq("author", author)) .order("desc") .paginate(opts); }, }); ``` -------------------------------- ### Custom Function with Role-Based Access Control Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Create a custom function builder that accepts an additional argument for role-based access control. This example demonstrates how to validate the user's role before proceeding with the query execution. ```typescript const myQueryBuilder = customQuery(query, { args: {}, input: async (ctx, args, { role }: { role: "admin" | "user" }) => { const user = await getUser(ctx); if (role === "admin" && user.role !== "admin") { throw new Error("You are not an admin"); } if (role === "user" && !user) { throw new Error("You must be logged in to access this query"); } return { ctx: { user }, args: {} }; }, }); const myAdminQuery = myQueryBuilder({ role: "admin", args: {}, handler: async (ctx, args) => { // ... }, }); ``` -------------------------------- ### Testing Convex Functions with convex-test and vitest Source: https://github.com/get-convex/convex-helpers/blob/main/convex/_generated/ai/guidelines.md Use this pattern to test Convex functions. Ensure `vitest` is configured with `environment: "edge-runtime"` and pass a module map from `import.meta.glob` to `convexTest`. ```typescript /// import { convexTest } from "convex-test"; import { expect, test } from "vitest"; import { api } from "./_generated/api"; import schema from "./schema"; const modules = import.meta.glob("./**/*.ts"); test("some behavior", async () => { const t = convexTest(schema, modules); await t.mutation(api.messages.send, { body: "Hi!", author: "Sarah" }); const messages = await t.query(api.messages.list); expect(messages).toMatchObject([{ body: "Hi!", author: "Sarah" }]); }); ``` -------------------------------- ### Get Multiple Documents or Throw Error Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/api-reference/server-relationships.md Use `getAllOrThrow` to fetch multiple documents by their IDs. This function guarantees that all requested documents exist, throwing an error if any are missing. The table name can be specified or inferred from the IDs. ```typescript import { getAllOrThrow } from "convex-helpers/server/relationships"; const userIds = [userId1, userId2, userId3]; const users = await getAllOrThrow(ctx.db, "users", userIds); // All three users guaranteed to exist const comments = await getAllOrThrow(ctx.db, commentIds); ``` -------------------------------- ### Hono Integration from convex-helpers/server/hono Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Import types and utilities for integrating Convex with the Hono framework. This enables building HTTP APIs alongside your Convex functions. ```typescript import { HttpRouterWithHono, HonoWithConvex } from "convex-helpers/server/hono"; ``` -------------------------------- ### Testing Helper from convex-helpers/testing Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Import the ConvexTestingHelper for writing tests. This utility provides tools to mock and interact with Convex functions during testing. ```typescript // Testing import { ConvexTestingHelper } from "convex-helpers/testing"; ``` -------------------------------- ### Streaming and Pagination from convex-helpers/server/stream Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/INDEX.md Import utilities for handling streaming data and paginated queries. These are essential for efficiently retrieving large datasets. ```typescript import { stream, paginator } from "convex-helpers/server/stream"; ``` -------------------------------- ### Generate TypeScript API Spec Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Run this command in your Convex folder to generate a TypeScript API spec file for type-safe external repository usage. Connects to dev deployment by default. ```bash npx convex-helpers ts-api-spec ``` -------------------------------- ### Querying File Metadata from _storage System Table Source: https://github.com/get-convex/convex-helpers/blob/main/convex/_generated/ai/guidelines.md Access file metadata by querying the `_storage` system table using `ctx.db.system.get`. This replaces the deprecated `ctx.storage.getMetadata` call. The `FileMetadata` type provides expected fields. ```typescript import { query } from "./_generated/server"; import { Id } from "./_generated/dataModel"; type FileMetadata = { _id: Id<"_storage">; _creationTime: number; contentType?: string; sha256: string; size: number; } export const exampleQuery = query({ args: { fileId: v.id("_storage") }, handler: async (ctx, args) => { const metadata: FileMetadata | null = await ctx.db.system.get("_storage", args.fileId); console.log(metadata); return null; }, }); ``` -------------------------------- ### Initialize SessionProvider in React Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Wrap your Convex app with SessionProvider to enable client-side session tracking. This is required before using session-related hooks. ```javascript import { SessionProvider } from "convex-helpers/react/sessions"; //... ; ``` -------------------------------- ### Paginate Messages by Authors using QueryStreams Source: https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md Constructs a stream of messages by multiple authors, merges them, and paginates the result. Requires a schema with a 'messages' table and a 'by_author' index. ```typescript import { stream, mergedStream } from "convex-helpers/server/stream"; import schema from "./schema"; // schema has messages: defineTable(...).withIndex("by_author", ["author"]) export const listForAuthors = query({ args: { authors: v.array(v.id("users")), paginationOpts: paginationOptsValidator, }, handler: async (ctx, { authors, paginationOpts }) => { // This is an array of streams, where each stream consists of messages by a // single author. const authorStreams = authors.map((author) => stream(ctx.db, schema) .query("messages") .withIndex("by_author", (q) => q.eq("author", author)), ); // Create a new stream of all messages authored by users in `args.authors`, // ordered by the "by_author" index (i.e. ["author", "_creationTime"]). const allAuthorsStream = mergedStream(authorStreams, [ "author", "_creationTime", ]); // Paginate the result. return await allAuthorsStream.paginate(paginationOpts); }, }); ``` -------------------------------- ### Advanced Server Patterns Source: https://github.com/get-convex/convex-helpers/blob/main/_autodocs/README.md Implement advanced server-side patterns including access control and routing. ```APIDOC ## Advanced Server Patterns ### Description Implement advanced server-side patterns including access control and routing. ### Features - `RowLevelSecurity`: Implement row-level access control. - `HttpRouterWithHono`: Integrate HTTP routing using the Hono framework. - `filter(array, predicate)`: Filter an array using a JavaScript predicate function. ```