miketromba / drizzle-pagination

Easily paginate your Drizzle ORM queries
MIT License
52 stars 3 forks source link

Previous page support? #4

Open oney opened 4 months ago

oney commented 4 months ago

Thank you for creating this package! It's very handy for paginating to the next page, but it seems to lack support for the previous page.

I'm using drizzle pagination with tRPC.

My code works, but it's still a bit tedious even with the help of drizzle-pagination. Do you have any plans to support generating hasNext, hasPrev, nextCursor, and prevCursor directly or through helper functions?

Here's what my code looks like:

const cursorMessageSchema = z.object({
  id: z.string(),
  createdAt: z.date(),
});

const parseCursor = cursorMessageSchema.strip();

export const messages = publicProcedure
  .input(
    z.object({
      chatroomId: z.string(),
      cursor: z.optional(cursorMessageSchema),
      direction: z.enum(["forward", "backward"]),
      limit: z.number().min(1).max(100).nullish(),
    }),
  )
  .query(async ({ ctx, input }) => {
    const { cursor, direction } = input;
    const limit = input.limit ?? 10;

    const items = await ctx.db.query.Message.findMany({
      ...withCursorPagination({
        where: eq(Message.chatroomId, input.chatroomId),
        limit: limit + 1,
        cursors: [
          [Message.createdAt, toOrder(direction), cursor?.createdAt],
          [Message.id, toOrder(direction), cursor?.id],
        ],
      }),
      with: {
        // ...
      },
    });
    const firstItem = items[0];

    const prevItems = await ctx.db.query.Message.findMany({
      ...withCursorPagination({
        where: eq(Message.chatroomId, input.chatroomId),
        limit: 1,
        cursors: [
          [Message.createdAt, toOrder(direction, true), firstItem?.createdAt],
          [Message.id, toOrder(direction, true), firstItem?.id],
        ],
      }),
    });

    const hasNext = items.length === limit ? false : (items.pop(), true);
    const hasPrev = prevItems.length !== 0;

    return {
      items,
      hasNext,
      hasPrev,
      nextCursor: items[items.length - 1]
        ? parseCursor.parse(items[items.length - 1])
        : undefined,
      prevCursor: items[0] ? parseCursor.parse(items[0]) : undefined,
    };
    // Next/prev cursors always exist (if not empty) even if there's no next/prev page available at the time of querying
  });

function toOrder(direction: "forward" | "backward", reverse = false) {
  return (direction === "forward") === !reverse ? "desc" : "asc";
}
miketromba commented 3 months ago

I don't have plans right now, but my quick reaction is:

I dislike the approach you are taking here (making an additional query to generate the hasPrev value server-side) and suggest delegating this to the client to infer for itself (as described in the first bullet point above).

Next/prev cursors always exist (if not empty) even if there's no next/prev page available at the time of querying

Just curious, why is this a requirement in your system?