Ali Madooei
Ali Madooei
CCConvex Community
Created by Ali Madooei on 12/22/2024 in #support-community
Paginated Queries correctly when multiple concurrent mutations
I need help implementing Paginated Queries correctly when multiple concurrent mutations (create, delete, and update operations) affect the record count and pagination cursor. Consider a ChatGPT-like interface with a list of messages. I'm using paginated queries to fetch the N most recent messages and display them to the user with an option to "Load earlier messages." When a user edits a message, the conversation restarts from that point. For example, in a chat with 100 messages where the last 20 are displayed due to pagination, editing message #85 would trigger two actions: the backend would delete all messages after #85, and the AI would generate a new response to the edited message. This would result in a chat containing 86 messages. Currently, my "update message" mutation performs the following: 1. Update the edited message 2. Add a placeholder message for the AI response 3. Trigger (schedule) an action to delete all messages after the edited message and before the placeholder AI message. 4. Trigger (schedule) an action to connect to OpenAI API and incrementally update the placeholder AI message as the response streams in. My pagination is messed up! As these updates are happening, usePaginatedQuery keeps getting triggered, and I constantly see a "hit error" saying "InvalidCursor: Tried to run a query starting from a cursor, but it looks like this cursor is from a different query."
14 replies
CCConvex Community
Created by Ali Madooei on 12/2/2024 in #support-community
Internal queries/mutations/actions vs helper functions
Hi everyone! I'm trying to understand the difference between internal queries/mutations/actions and regular helper functions. I'm going to write down my thoughts here, in a contrived example., and I hope you can help me understand it better. Thanks!
// convex/somefile.ts
import { internalQuery, query } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

// Do we have to `export` it? Or the 'query' factory will make it public irrespective?
export const myPublicQuery = query({
args: {
a: v.string(), // some argument; it will be validated
},
handler: async (ctx, args) => {

// I can call `myPrivateQuery` here
await ctx.runQuery(internal.somefile.myPrivateQuery, args);

// I can also directly call `myPrivateQuery` here like a regular function
await myPrivateQuery(ctx, args);

// So, should I use `runQuery` or call the internal query directly?

// I can call `myHelper` here as well
await myHelper(ctx, args);

// So, why when would I want a helper to be internal query/mutation/action?
// Is the difference only in the arguments validation?
},
});


// Do we need the `export` here? Even in other files it can be accessed through
// `internal.somefile.myPrivateQuery` as long as it is an `internalQuery`, right?
export const myPrivateQuery = internalQuery({
args: {
a: v.string(), // This argument will be validated
},
handler: async (ctx, args) => {
// Do something
},
});


// Doesn't have to be exported, but can be if you want to use it in other files
export const myHelper = (ctx: QueryCtx, { a }: { a: string }) => {
// Do the exact same thins as in `myPrivateQuery`
// If I want to validate the arguments, I must do it myself using e.g. Zod
};
// convex/somefile.ts
import { internalQuery, query } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

// Do we have to `export` it? Or the 'query' factory will make it public irrespective?
export const myPublicQuery = query({
args: {
a: v.string(), // some argument; it will be validated
},
handler: async (ctx, args) => {

// I can call `myPrivateQuery` here
await ctx.runQuery(internal.somefile.myPrivateQuery, args);

// I can also directly call `myPrivateQuery` here like a regular function
await myPrivateQuery(ctx, args);

// So, should I use `runQuery` or call the internal query directly?

// I can call `myHelper` here as well
await myHelper(ctx, args);

// So, why when would I want a helper to be internal query/mutation/action?
// Is the difference only in the arguments validation?
},
});


// Do we need the `export` here? Even in other files it can be accessed through
// `internal.somefile.myPrivateQuery` as long as it is an `internalQuery`, right?
export const myPrivateQuery = internalQuery({
args: {
a: v.string(), // This argument will be validated
},
handler: async (ctx, args) => {
// Do something
},
});


// Doesn't have to be exported, but can be if you want to use it in other files
export const myHelper = (ctx: QueryCtx, { a }: { a: string }) => {
// Do the exact same thins as in `myPrivateQuery`
// If I want to validate the arguments, I must do it myself using e.g. Zod
};
5 replies