Ali Madooei
Ali Madooei
CCConvex Community
Created by Ali Madooei on 12/22/2024 in #support-community
Paginated Queries correctly when multiple concurrent mutations
Thanks Lee! Yes, the problem was somewhere else in my code and it is resolved now (irrelevant to this discussion). So, I can confirm for anyone reading this thread, the with paginate query was due to using Date.now()
14 replies
CCConvex Community
Created by Ali Madooei on 12/22/2024 in #support-community
Paginated Queries correctly when multiple concurrent mutations
@lee if you have any thoughts on why updating any of the messages does not update the React state, please let me know. I'll try to figure it out too, and if I find the problem, will report here for reference for others. Thanks again. PS. by update does not work I mean, let's say I have a list of messages. If I even directly update a message in the database, I don't see the update in the app. The "reactivity" which was creating the realtime feature is lost! And I'm not sure if the problem is the paginated query or something else.
14 replies
CCConvex Community
Created by Ali Madooei on 12/22/2024 in #support-community
Paginated Queries correctly when multiple concurrent mutations
Going back to chaining the queries, I thought I can use Number.MAX_SAFE_INTEGER instead of Date.now() and it seems to work:
const results: PaginationResult<Doc<"messages">> = await ctx.db
.query("messages")
.withIndex(
"by_chat_id",
(
q: IndexRangeBuilder<Doc<"messages">, ["chatId", "_creationTime"], 0>,
) => {

return q
.eq("chatId", chatId)
.gt("_creationTime", afterThisCreationTime ?? 0)
.lt(
"_creationTime",
beforeThisCreationTime ?? Number.MAX_SAFE_INTEGER,
);
},
)
.order(sortOrder)
.paginate(paginationOpts);
const results: PaginationResult<Doc<"messages">> = await ctx.db
.query("messages")
.withIndex(
"by_chat_id",
(
q: IndexRangeBuilder<Doc<"messages">, ["chatId", "_creationTime"], 0>,
) => {

return q
.eq("chatId", chatId)
.gt("_creationTime", afterThisCreationTime ?? 0)
.lt(
"_creationTime",
beforeThisCreationTime ?? Number.MAX_SAFE_INTEGER,
);
},
)
.order(sortOrder)
.paginate(paginationOpts);
14 replies
CCConvex Community
Created by Ali Madooei on 12/22/2024 in #support-community
Paginated Queries correctly when multiple concurrent mutations
Thanks, Lee! This partially fixed my problem. I don't have InvalidCursor anymore but updates to messages (e.g., updating the AI placeholder message as the AI response streams) does not work; looks like the React state does not change. I don't know why but I'm going to try to figure it out. For anyone looking at this thread, I thought I should provide the following information. This assignment doesn't work: q = q.lt("_creationTime", beforeThisCreationTime);. So I ended up doing this:
const results: PaginationResult<Doc<"messages">> = await ctx.db
.query("messages")
.withIndex(
"by_chat_id",
(
q: IndexRangeBuilder<Doc<"messages">, ["chatId", "_creationTime"], 0>,
) => {
let q1, q2, q3;

q1 = q.eq("chatId", chatId);

if (afterThisCreationTime) {
q2 = q1.gt("_creationTime", afterThisCreationTime);
}

if (beforeThisCreationTime) {
q3 = q2 ? q2.lt("_creationTime", beforeThisCreationTime) :
q1.lt("_creationTime", beforeThisCreationTime);
}

return q3 || q2 || q1;
},
)
.order(sortOrder)
.paginate(paginationOpts);
const results: PaginationResult<Doc<"messages">> = await ctx.db
.query("messages")
.withIndex(
"by_chat_id",
(
q: IndexRangeBuilder<Doc<"messages">, ["chatId", "_creationTime"], 0>,
) => {
let q1, q2, q3;

q1 = q.eq("chatId", chatId);

if (afterThisCreationTime) {
q2 = q1.gt("_creationTime", afterThisCreationTime);
}

if (beforeThisCreationTime) {
q3 = q2 ? q2.lt("_creationTime", beforeThisCreationTime) :
q1.lt("_creationTime", beforeThisCreationTime);
}

return q3 || q2 || q1;
},
)
.order(sortOrder)
.paginate(paginationOpts);
I think the IndexRangeBuilder works by updating the type parameter FieldNum so to enforce that we proceed through the fields in index order. I don't know if there is a more elegant way of doing this (excpet going back to chaining).
14 replies
CCConvex Community
Created by Ali Madooei on 12/22/2024 in #support-community
Paginated Queries correctly when multiple concurrent mutations
I'm using this custom hook on the frontend:
import { api } from "@api/_generated/api";
import { Id } from "@api/_generated/dataModel";
import { usePaginatedQuery } from "convex/react";

import { MessageType } from "@app/messages/types/message";
import { useEffect, useState } from "react";

const INITIAL_NUM_ITEMS = 20;
const LOAD_MORE_NUM_ITEMS = 10;

export function useQueryMessages(chatId: string) {
const [messages, setMessages] = useState<MessageType[]>([]);
const { results, status, loadMore } = usePaginatedQuery(
// @ts-ignore
api.messages.queries.getAll,
{
chatId: chatId as Id<"chats">,
},
{
initialNumItems: INITIAL_NUM_ITEMS,
},
);

useEffect(() => {
if (results) {
setMessages(results.reverse());
}
}, [results]);

return {
// data: results as MessageType[],
data: messages,
loading: status === "LoadingFirstPage",
error: results === null,
status,
loadMore: () => loadMore(LOAD_MORE_NUM_ITEMS),
};
}
import { api } from "@api/_generated/api";
import { Id } from "@api/_generated/dataModel";
import { usePaginatedQuery } from "convex/react";

import { MessageType } from "@app/messages/types/message";
import { useEffect, useState } from "react";

const INITIAL_NUM_ITEMS = 20;
const LOAD_MORE_NUM_ITEMS = 10;

export function useQueryMessages(chatId: string) {
const [messages, setMessages] = useState<MessageType[]>([]);
const { results, status, loadMore } = usePaginatedQuery(
// @ts-ignore
api.messages.queries.getAll,
{
chatId: chatId as Id<"chats">,
},
{
initialNumItems: INITIAL_NUM_ITEMS,
},
);

useEffect(() => {
if (results) {
setMessages(results.reverse());
}
}, [results]);

return {
// data: results as MessageType[],
data: messages,
loading: status === "LoadingFirstPage",
error: results === null,
status,
loadMore: () => loadMore(LOAD_MORE_NUM_ITEMS),
};
}
14 replies
CCConvex Community
Created by Ali Madooei on 12/22/2024 in #support-community
Paginated Queries correctly when multiple concurrent mutations
This is the actual query that calls the above helper:
/**
* Get all messages for the authenticated user, optionally sorted by the given order
*/
export const getAll = query({
args: {
paginationOpts: paginationOptsValidator,
chatId: v.id("chats"),
sortOrder: v.optional(SortOrder),
},
handler: async (ctx, args) => {
const { chatId, sortOrder } = args;
const userId = await authenticationGuard(ctx);
const chat = await getChatById(ctx, chatId);
chatOwnershipCheck(userId, chat.userId);
return await getAllMessages(ctx, args.paginationOpts, chatId, sortOrder);
},
});
/**
* Get all messages for the authenticated user, optionally sorted by the given order
*/
export const getAll = query({
args: {
paginationOpts: paginationOptsValidator,
chatId: v.id("chats"),
sortOrder: v.optional(SortOrder),
},
handler: async (ctx, args) => {
const { chatId, sortOrder } = args;
const userId = await authenticationGuard(ctx);
const chat = await getChatById(ctx, chatId);
chatOwnershipCheck(userId, chat.userId);
return await getAllMessages(ctx, args.paginationOpts, chatId, sortOrder);
},
});
14 replies
CCConvex Community
Created by Ali Madooei on 12/22/2024 in #support-community
Paginated Queries correctly when multiple concurrent mutations
Sure! This is the helper function that my query calls:
export async function getAllMessages(
ctx: QueryCtx,
paginationOpts: PaginationOptsType,
chatId: Id<"chats">,
sortOrder?: SortOrderType,
afterThisCreationTime?: number,
beforeThisCreationTime?: number
) {
sortOrder = sortOrder || "desc"; // For chat history, we want the most recent messages first.

const results: PaginationResult<Doc<"messages">> = await ctx.db
.query("messages")
.withIndex("by_chat_id", (q) =>
q
.eq("chatId", chatId)
.gt("_creationTime", afterThisCreationTime || 0)
.lt("_creationTime", beforeThisCreationTime || Date.now() + 1)
)
.order(sortOrder)
.paginate(paginationOpts);

return {
...results,
page: results.page, // In case we want to modify the records.
};
}
export async function getAllMessages(
ctx: QueryCtx,
paginationOpts: PaginationOptsType,
chatId: Id<"chats">,
sortOrder?: SortOrderType,
afterThisCreationTime?: number,
beforeThisCreationTime?: number
) {
sortOrder = sortOrder || "desc"; // For chat history, we want the most recent messages first.

const results: PaginationResult<Doc<"messages">> = await ctx.db
.query("messages")
.withIndex("by_chat_id", (q) =>
q
.eq("chatId", chatId)
.gt("_creationTime", afterThisCreationTime || 0)
.lt("_creationTime", beforeThisCreationTime || Date.now() + 1)
)
.order(sortOrder)
.paginate(paginationOpts);

return {
...results,
page: results.page, // In case we want to modify the records.
};
}
14 replies
CCConvex Community
Created by Ali Madooei on 12/2/2024 in #support-community
Internal queries/mutations/actions vs helper functions
Thanks Lee and sshader. This was very helpful - I appreciate it.
5 replies