feat: switch to cursor pagination (#1884)

* add cursor pagination function

* support custom order modifier
* refactor returned object

* feat(db): migrate paginated endpoints to cursor-based pagination

* sync

* support hasPrevPage boolean

* feat(client): migrate pagination from offset to cursor-based

* support beforeCursor/prevCursor

* wrap search results in items array for API consistency
This commit is contained in:
Philip Okugbe
2026-01-30 19:28:54 +00:00
committed by GitHub
parent 96ed98619f
commit 78b1c1a453
49 changed files with 792 additions and 341 deletions
@@ -250,12 +250,10 @@ export function useGetSidebarPagesQuery(
return useInfiniteQuery({
queryKey: ["sidebar-pages", data],
enabled: !!data?.pageId || !!data?.spaceId,
queryFn: ({ pageParam }) => getSidebarPages({ ...data, page: pageParam }),
initialPageParam: 1,
getPreviousPageParam: (firstPage) =>
firstPage.meta.hasPrevPage ? firstPage.meta.page - 1 : undefined,
queryFn: ({ pageParam }) => getSidebarPages({ ...data, cursor: pageParam }),
initialPageParam: undefined,
getNextPageParam: (lastPage) =>
lastPage.meta.hasNextPage ? lastPage.meta.page + 1 : undefined,
lastPage.meta?.nextCursor ?? undefined,
});
}
@@ -263,13 +261,11 @@ export function useGetRootSidebarPagesQuery(data: SidebarPagesParams) {
return useInfiniteQuery({
queryKey: ["root-sidebar-pages", data.spaceId],
queryFn: async ({ pageParam }) => {
return getSidebarPages({ spaceId: data.spaceId, page: pageParam });
return getSidebarPages({ spaceId: data.spaceId, cursor: pageParam });
},
initialPageParam: 1,
getPreviousPageParam: (firstPage) =>
firstPage.meta.hasPrevPage ? firstPage.meta.page - 1 : undefined,
initialPageParam: undefined,
getNextPageParam: (lastPage) =>
lastPage.meta.hasNextPage ? lastPage.meta.page + 1 : undefined,
lastPage.meta?.nextCursor ?? undefined,
});
}
@@ -72,22 +72,19 @@ export async function getSidebarPages(
export async function getAllSidebarPages(
params: SidebarPagesParams,
): Promise<InfiniteData<IPagination<IPage>, unknown>> {
let page = 1;
let hasNextPage = false;
let cursor: string | undefined = undefined;
const pages: IPagination<IPage>[] = [];
const pageParams: number[] = [];
const pageParams: (string | undefined)[] = [];
do {
const req = await api.post("/pages/sidebar-pages", { ...params, page: page });
const req = await api.post("/pages/sidebar-pages", { ...params, cursor });
const data: IPagination<IPage> = req.data;
pages.push(data);
pageParams.push(page);
pageParams.push(cursor);
hasNextPage = data.meta.hasNextPage;
page += 1;
} while (hasNextPage);
cursor = data.meta.nextCursor ?? undefined;
} while (cursor);
return {
pageParams,
@@ -30,15 +30,15 @@ import { useState } from "react";
import TrashPageContentModal from "@/features/page/trash/components/trash-page-content-modal";
import { UserInfo } from "@/components/common/user-info.tsx";
import Paginate from "@/components/common/paginate.tsx";
import { usePaginateAndSearch } from "@/hooks/use-paginate-and-search";
import { useCursorPaginate } from "@/hooks/use-cursor-paginate";
export default function Trash() {
const { t } = useTranslation();
const { spaceSlug } = useParams();
const { page, setPage } = usePaginateAndSearch();
const { cursor, goNext, goPrev } = useCursorPaginate();
const { data: space } = useGetSpaceBySlugQuery(spaceSlug);
const { data: deletedPages, isLoading } = useDeletedPagesQuery(space?.id, {
page, limit: 50
cursor, limit: 50
});
const restorePageMutation = useRestorePageMutation();
const deletePageMutation = useDeletePageMutation();
@@ -206,10 +206,10 @@ export default function Trash() {
{deletedPages && deletedPages.items.length > 0 && (
<Paginate
currentPage={page}
hasPrevPage={deletedPages.meta.hasPrevPage}
hasNextPage={deletedPages.meta.hasNextPage}
onPageChange={setPage}
hasPrevPage={deletedPages.meta?.hasPrevPage}
hasNextPage={deletedPages.meta?.hasNextPage}
onNext={() => goNext(deletedPages.meta?.nextCursor)}
onPrev={goPrev}
/>
)}
</Stack>
@@ -62,7 +62,7 @@ export interface ICopyPageToSpace {
export interface SidebarPagesParams {
spaceId?: string;
pageId?: string;
page?: number; // pagination
cursor?: string;
}
export interface IPageInput {