mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-05-23 07:32:28 -04:00
Create keyset pagination function
This commit is contained in:
@@ -5,3 +5,5 @@ export * from "./language";
|
||||
export * from "./resource";
|
||||
export * from "./filters";
|
||||
export * from "./page";
|
||||
export * from "./sort";
|
||||
export * from "./keyset-paginate";
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { NonEmptyArray, Sort } from "./sort";
|
||||
import { eq, or, type Column, and, gt, lt } from "drizzle-orm";
|
||||
|
||||
type Table<Name extends string> = Record<Name, Column>;
|
||||
|
||||
// Create a filter (where) expression on the query to skip everything before/after the referenceID.
|
||||
// The generalized expression for this in pseudocode is:
|
||||
// (x > a) OR
|
||||
// (x = a AND y > b) OR
|
||||
// (x = a AND y = b AND z > c) OR...
|
||||
//
|
||||
// Of course, this will be a bit more complex when ASC and DESC are mixed.
|
||||
// Assume x is ASC, y is DESC, and z is ASC:
|
||||
// (x > a) OR
|
||||
// (x = a AND y < b) OR
|
||||
// (x = a AND y = b AND z > c) OR...
|
||||
export const keysetPaginate = <
|
||||
const T extends NonEmptyArray<string>,
|
||||
const Remap extends Partial<Record<T[number], string>>,
|
||||
>({
|
||||
table,
|
||||
sort,
|
||||
after,
|
||||
}: {
|
||||
table: Table<"pk" | Sort<T, Remap>[number]["key"]>;
|
||||
after: string | undefined;
|
||||
sort: Sort<T, Remap>;
|
||||
}) => {
|
||||
if (!after) return undefined;
|
||||
const cursor: Record<string, string | number> = JSON.parse(
|
||||
Buffer.from(after, "base64").toString("utf-8"),
|
||||
);
|
||||
|
||||
// TODO: Add an outer query >= for perf
|
||||
// PERF: See https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic
|
||||
let where = undefined;
|
||||
let previous = undefined;
|
||||
for (const by of [...sort, { key: "pk" as const, desc: false }]) {
|
||||
const cmp = by.desc ? lt : gt;
|
||||
where = or(where, and(previous, cmp(table[by.key], cursor[by.key])));
|
||||
previous = and(previous, eq(table[by.key], cursor[by.key]));
|
||||
}
|
||||
|
||||
return where;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { t } from "elysia";
|
||||
|
||||
type Sort<
|
||||
export type Sort<
|
||||
T extends string[],
|
||||
Remap extends Partial<Record<T[number], string>>,
|
||||
> = {
|
||||
key: Exclude<T[number], keyof Remap> | Remap[keyof Remap];
|
||||
key: Exclude<T[number], keyof Remap> | NonNullable<Remap[keyof Remap]>;
|
||||
desc: boolean;
|
||||
}[];
|
||||
|
||||
type NonEmptyArray<T> = [T, ...T[]];
|
||||
export type NonEmptyArray<T> = [T, ...T[]];
|
||||
|
||||
export const Sort = <
|
||||
const T extends NonEmptyArray<string>,
|
||||
@@ -44,7 +44,7 @@ export const Sort = <
|
||||
return sort.map((x) => {
|
||||
const desc = x[0] === "-";
|
||||
const key = (desc ? x.substring(1) : x) as T[number];
|
||||
if (key in remap) return { key: remap[key], desc };
|
||||
if (key in remap) return { key: remap[key]!, desc };
|
||||
return { key: key as Exclude<typeof key, keyof Remap>, desc };
|
||||
});
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user