diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..13ee2b0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "nuxt.isNuxtApp": false +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fb1cdec..57eb385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # drizzle-cursor +## 0.1.0 + +### Minor Changes + +- - Add `parse()`/`serialize()` function and `config.parse()`/`config.serialize()` methods to handle base64 tokens + + - Refactor `config.where` so intead of being an object is a clousure function which enables to use the same instance of `generateCursor()` accross multiple calls calling `.where()` without and with arguments. + - Remove second argument from `generateCursor()` which is now used as the argument of `.where()` + ## 0.0.3 ### Patch Changes diff --git a/README.md b/README.md index 6302fa1..6a0b650 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,56 @@ Utils to generate cursor based pagination on Drizzle ORM -Check example at: [test/example.ts](./test/example.ts) \ No newline at end of file +Check example at: [test/example.ts](./test/example.ts) + +Use like: + +```ts +const cursorConfig: CursorConfig = { + cursors: [ + { order: "ASC", key: "lastName", schema: schema.users.lastName }, + { order: "ASC", key: "firstName", schema: schema.users.firstName }, + { order: "ASC", key: "middleName", schema: schema.users.middleName }, + ], + primaryCursor: { order: "ASC", key: "id", schema: schema.users.id }, +}; + +const cursor = generateCursor(cursorConfig); + +const page1 = await db + .select({ + lastName: schema.users.lastName, + firstName: schema.users.firstName, + middleName: schema.users.middleName, + id: schema.users.id, + }) + .from(schema.users) + .orderBy(...cursor.orderBy) // Always include the order + .where(cursor.where()) // .where() is called empty the first time, meaning "there's not previous records" + .limit(page_size); + +const page2 = await db + .select() // .select() can vary while includes the needed data to create next curso (the same as the tables listed in primaryCursor and cursors) + .from(schema.users) + .orderBy(...cursor.orderBy) + .where(cursor.where(page1.at(-1))) // last record of previous query (or any record "before: the one you want to start with) + .limit(page_size); + +const lastToken = cursor.serialize(page2.at(-1)); // use serialize method/function to send tokens to your FE +const lastItem = cursor.parse(lastToken); // use parse method/function to transform back in an object + +const page3 = await db.query.users.findMany({ + // It also works with Relational Queries + columns: { + // Be sure to include the data needed to create the cursor if using columns + lastName: true, + firstName: true, + middleName: true, + id: true, + }, + orderBy: cursor.orderBy, // no need to destructure here + where: cursor.where(lastToken), // .where() also accepts the string token directly, no need to pre-parse it (at least you want to run extra validations) + limit: page_size, +}); + +``` \ No newline at end of file diff --git a/package.json b/package.json index f0363bf..d05bd41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-cursor", - "version": "0.0.3", + "version": "0.1.0", "description": "Utils for Drizzle ORM cursor based pagination", "main": "dist/index.js", "module": "dist/index.mjs", @@ -37,6 +37,7 @@ }, "devDependencies": { "@changesets/cli": "^2.26.2", + "@total-typescript/ts-reset": "^0.5.1", "@types/better-sqlite3": "^7.6.4", "@types/node": "^20.5.9", "better-sqlite3": "^8.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d9c0ad..c5c2acc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ devDependencies: '@changesets/cli': specifier: ^2.26.2 version: 2.26.2 + '@total-typescript/ts-reset': + specifier: ^0.5.1 + version: 0.5.1 '@types/better-sqlite3': specifier: ^7.6.4 version: 7.6.4 @@ -561,6 +564,10 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true + /@total-typescript/ts-reset@0.5.1: + resolution: {integrity: sha512-AqlrT8YA1o7Ff5wPfMOL0pvL+1X+sw60NN6CcOCqs658emD6RfiXhF7Gu9QcfKBH7ELY2nInLhKSCWVoNL70MQ==} + dev: true + /@types/better-sqlite3@7.6.4: resolution: {integrity: sha512-dzrRZCYPXIXfSR1/surNbJ/grU3scTaygS0OMzjlGf71i9sc2fGyHPXXiXmEvNIoE0cGwsanEFMVJxPXmco9Eg==} dependencies: diff --git a/src/generateCursor.ts b/src/generateCursor.ts new file mode 100644 index 0000000..003a4c6 --- /dev/null +++ b/src/generateCursor.ts @@ -0,0 +1,62 @@ +import { SQL, and, asc, desc, eq, gt, lt, or } from "drizzle-orm"; + +import type { CursorConfig } from "./types"; +import { generateSubArrays } from "./utils"; +import { parse } from "./parse"; +import { serialize } from "./serialize"; + +export const generateCursor = (config: CursorConfig) => { + const { cursors = [], primaryCursor } = config; + const orderBy: Array = []; + for (const { order = "ASC", schema } of [...cursors, primaryCursor]) { + const fn = order === "ASC" ? asc : desc; + const sql = fn(schema); + orderBy.push(sql); + } + return { + orderBy, + where: (lastPreviousItemData?: Record | string | null) => { + if (!lastPreviousItemData) { + return undefined; + } + + const data = + typeof lastPreviousItemData === "string" + ? parse(config, lastPreviousItemData) + : lastPreviousItemData; + + if (!data) { + return undefined; + } + + const matrix = generateSubArrays([...cursors, primaryCursor]); + + const ors: Array = []; + for (const posibilities of matrix) { + const ands: Array = []; + for (const cursor of posibilities) { + const lastValue = cursor === posibilities?.at(-1); + const { order = "ASC", schema, key } = cursor; + const fn = order === "ASC" ? gt : lt; + const sql = !lastValue + ? eq(schema, data[key]) + : fn(schema, data[key]); + ands.push(sql); + } + const _and = and(...ands); + if (!_and) { + continue; + } + ors.push(_and); + } + const where = or(...ors); + + return where; + }, + parse: (cursor: string) => parse(config, cursor), + serialize: (data?: Record | null) => + serialize(config, data), + }; +}; + +export default generateCursor; diff --git a/src/index.ts b/src/index.ts index 3925dee..30e7b17 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,55 +1,5 @@ -import { AnyColumn, SQL, and, asc, desc, eq, gt, lt, or } from "drizzle-orm"; - -import { generateSubArrays } from "./utils"; - -export type Cursor = { order?: "ASC" | "DESC"; key: string; schema: AnyColumn }; - -export type CursorConfig = { - primaryCursor: Cursor; - cursors?: Array; -}; - -export const generateCursor = ( - { cursors = [], primaryCursor }: CursorConfig, - lastPreviousItemData?: Record -) => { - const orderBy: Array = []; - for (const { order = "ASC", schema } of [...cursors, primaryCursor]) { - const fn = order === "ASC" ? asc : desc; - const sql = fn(schema); - orderBy.push(sql); - } - - if (!lastPreviousItemData) { - return { - orderBy, - where: undefined, - }; - } - - const matrix = generateSubArrays([...cursors, primaryCursor]); - - const ors: Array = []; - for (const posibilities of matrix) { - const ands: Array = []; - for (const cursor of posibilities) { - const lastValue = cursor === posibilities?.at(-1); - const { order = "ASC", schema, key } = cursor; - const fn = order === "ASC" ? gt : lt; - const sql = !lastValue - ? eq(schema, lastPreviousItemData[key]) - : fn(schema, lastPreviousItemData[key]); - ands.push(sql); - } - const _and = and(...ands); - if (!_and) { - continue; - } - ors.push(_and); - } - const where = or(...ors); - return { - orderBy, - where, - }; -}; +export * from "./types"; +export * from "./generateCursor"; +export { generateCursor as default } from "./generateCursor"; +export { parse } from "./parse"; +export { serialize } from "./serialize"; diff --git a/src/parse.ts b/src/parse.ts new file mode 100644 index 0000000..f33b144 --- /dev/null +++ b/src/parse.ts @@ -0,0 +1,26 @@ +import "@total-typescript/ts-reset"; + +import type { CursorConfig } from "./types"; + +export function parse< + T extends Record = Record +>( + { primaryCursor, cursors = [] }: CursorConfig, + cursor?: string | null +): T | null { + if (!cursor) { + return null; + } + + const keys = [primaryCursor, ...cursors].map((cursor) => cursor.key); + const data = JSON.parse(atob(cursor)) as T; + + const item = keys.reduce((acc, key) => { + const value = data[key]; + acc[key] = value; + return acc; + }, {} as Record); + return item as T; +} + +export default parse; diff --git a/src/serialize.ts b/src/serialize.ts new file mode 100644 index 0000000..919fd84 --- /dev/null +++ b/src/serialize.ts @@ -0,0 +1,25 @@ +import "@total-typescript/ts-reset"; + +import type { CursorConfig } from "./types"; + +export function serialize< + T extends Record = Record +>( + { primaryCursor, cursors = [] }: CursorConfig, + data?: T | null +): string | null { + if (!data) { + return null; + } + + const keys = [primaryCursor, ...cursors].map((cursor) => cursor.key); + const item = keys.reduce((acc, key) => { + const value = data[key]; + acc[key] = value; + return acc; + }, {} as Record); + + return btoa(JSON.stringify(item)); +} + +export default serialize; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..a20dffd --- /dev/null +++ b/src/types.ts @@ -0,0 +1,8 @@ +import type { AnyColumn } from "drizzle-orm"; + +export type Cursor = { order?: "ASC" | "DESC"; key: string; schema: AnyColumn }; + +export type CursorConfig = { + primaryCursor: Cursor; + cursors?: Array; +}; diff --git a/src/utils.ts b/src/utils.ts index 38e76fb..3ed0228 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,5 @@ +import "@total-typescript/ts-reset"; + export function generateSubArrays(arr: ReadonlyArray): T[][] { const subArrays: T[][] = []; for (let i = 0; i < arr.length; i++) { diff --git a/test/db/index.ts b/test/db/index.ts index 60cd0f6..f9659bf 100644 --- a/test/db/index.ts +++ b/test/db/index.ts @@ -1,8 +1,7 @@ import * as schema from "./schema"; -import { BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3"; - import Database from "better-sqlite3"; +import { drizzle } from "drizzle-orm/better-sqlite3"; const sqlite = new Database("db.db"); export const db = drizzle(sqlite, { schema, logger: true }); diff --git a/test/db/schema.ts b/test/db/schema.ts index 7cf0bb9..1074ed8 100644 --- a/test/db/schema.ts +++ b/test/db/schema.ts @@ -1,12 +1,4 @@ -import { - AnySQLiteColumn, - integer, - numeric, - sqliteTable, - text, -} from "drizzle-orm/sqlite-core"; - -import { sql } from "drizzle-orm"; +import { integer, numeric, sqliteTable, text } from "drizzle-orm/sqlite-core"; export const users = sqliteTable("users", { id: integer("id").primaryKey({ autoIncrement: true }), diff --git a/test/example.ts b/test/example.ts index 961a652..e97552a 100644 --- a/test/example.ts +++ b/test/example.ts @@ -13,7 +13,8 @@ const cursorConfig: CursorConfig = { }; async function main() { - const cursorGenerated1 = generateCursor(cursorConfig); + const cursor = generateCursor(cursorConfig); + const data1 = await db .select({ lastName: schema.users.lastName, @@ -22,8 +23,8 @@ async function main() { id: schema.users.id, }) .from(schema.users) - .orderBy(...cursorGenerated1.orderBy) - .where(cursorGenerated1.where) + .orderBy(...cursor.orderBy) + .where(cursor.where()) .limit(page_size); const last1 = data1.at(-1); @@ -34,7 +35,6 @@ async function main() { console.log(data1); - const cursorGenerated2 = generateCursor(cursorConfig, last1); const data2 = await db .select({ lastName: schema.users.lastName, @@ -43,8 +43,8 @@ async function main() { id: schema.users.id, }) .from(schema.users) - .orderBy(...cursorGenerated2.orderBy) - .where(cursorGenerated2.where) + .orderBy(...cursor.orderBy) + .where(cursor.where(last1)) .limit(page_size); const last2 = data2.at(-1); @@ -56,7 +56,6 @@ async function main() { console.log("-- 2 --"); console.log(data2); - const cursorGenerated3 = generateCursor(cursorConfig, last2); const data3 = await db.query.users.findMany({ columns: { lastName: true, @@ -64,8 +63,8 @@ async function main() { middleName: true, id: true, }, - orderBy: cursorGenerated3.orderBy, - where: cursorGenerated3.where, + orderBy: cursor.orderBy, + where: cursor.where(last2), limit: page_size, }); @@ -77,6 +76,29 @@ async function main() { console.log("-- 3 --"); console.log(data3); + + const last3CursorString = cursor.serialize(last3); + const data4 = await db + .select({ + lastName: schema.users.lastName, + firstName: schema.users.firstName, + middleName: schema.users.middleName, + id: schema.users.id, + }) + .from(schema.users) + .orderBy(...cursor.orderBy) + .where(cursor.where(last3CursorString)) + .limit(page_size); + + const last4 = data4.at(-1); + + if (!last4) { + return; + } + + console.log("-- 4 --"); + console.log({ last3CursorString }); + console.log(data4); } main(); diff --git a/test/generateCursor.test.ts b/test/generateCursor.test.ts new file mode 100644 index 0000000..c962852 --- /dev/null +++ b/test/generateCursor.test.ts @@ -0,0 +1,1522 @@ +import { Cursor, generateCursor } from "../src"; +import { and, asc, desc, eq, gt, lt, or } from "drizzle-orm"; +import { describe, expect, test } from "vitest"; +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +const table = sqliteTable("users", { + id: integer("id").primaryKey({ autoIncrement: true }), + firstName: text("first_name"), + middleName: text("middle_name"), + lastName: text("last_name"), + phone: text("phone"), + email: text("email"), +}); + +describe("generateCursor", () => { + const primaryCursorDefault: Cursor = { key: "id", schema: table.id }; + const primaryCursorASC: Cursor = { + key: "id", + order: "ASC", + schema: table.id, + }; + const primaryCursorDESC: Cursor = { + key: "id", + order: "DESC", + schema: table.id, + }; + + describe("with only primaryCursor", () => { + describe("without previous data generates only orderBy and where is undefined", () => { + test("with primaryCursorDefault", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + }); + expect(cursor.orderBy).toEqual([asc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC", () => { + const cursor = generateCursor({ primaryCursor: primaryCursorASC }); + expect(cursor.orderBy).toEqual([asc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC", () => { + const cursor = generateCursor({ primaryCursor: primaryCursorDESC }); + expect(cursor.orderBy).toEqual([desc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + }); + describe("with previous data generates orderBy and where", () => { + const previousData = { + id: 1, + firstName: "John", + middleName: "Doe", + lastName: "Smith", + phone: "123456789", + email: "johndoe", + }; + + test("with primaryCursorDefault", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + }); + expect(cursor.orderBy).toEqual([asc(table.id)]); + expect(cursor.where(previousData)).toEqual( + and(or(gt(table.id, previousData.id))) + ); + }); + + test("with primaryCursorASC", () => { + const cursor = generateCursor({ primaryCursor: primaryCursorASC }); + expect(cursor.orderBy).toEqual([asc(table.id)]); + expect(cursor.where(previousData)).toEqual( + and(or(gt(table.id, previousData.id))) + ); + }); + + test("with primaryCursorDESC", () => { + const cursor = generateCursor({ primaryCursor: primaryCursorDESC }); + expect(cursor.orderBy).toEqual([desc(table.id)]); + expect(cursor.where(previousData)).toEqual( + and(or(lt(table.id, previousData.id))) + ); + }); + }); + }); + + describe("with one cursor", () => { + const cursorDefault: Cursor = { + key: "lastName", + schema: table.lastName, + }; + + const cursorASC: Cursor = { + key: "lastName", + order: "ASC", + schema: table.lastName, + }; + + const cursorDESC: Cursor = { + key: "lastName", + order: "DESC", + schema: table.lastName, + }; + + describe("without previous data generates only orderBy and where is undefined", () => { + test("with primaryCursorDefault and cursorDefault", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDefault], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), asc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorASC], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), asc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorDESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDESC], + }); + expect(cursor.orderBy).toEqual([desc(table.lastName), asc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorDefault", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDefault], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), asc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorASC], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), asc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorDESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDESC], + }); + expect(cursor.orderBy).toEqual([desc(table.lastName), asc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorDefault", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDefault], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), desc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorASC], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), desc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorDESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDESC], + }); + expect(cursor.orderBy).toEqual([desc(table.lastName), desc(table.id)]); + expect(cursor.where()).toBeUndefined(); + }); + }); + + describe("with previous data generates orderBy and where", () => { + const previousData = { + id: 1, + firstName: "John", + middleName: "Doe", + lastName: "Smith", + phone: "123456789", + email: "johndoe", + }; + + test("with primaryCursorDefault and cursorDefault", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDefault], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), asc(table.id)]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorASC], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), asc(table.id)]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorDESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDESC], + }); + expect(cursor.orderBy).toEqual([desc(table.lastName), asc(table.id)]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorDefault", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDefault], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), asc(table.id)]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorASC], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), asc(table.id)]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorDESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDESC], + }); + expect(cursor.orderBy).toEqual([desc(table.lastName), asc(table.id)]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorDefault", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDefault], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), desc(table.id)]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorASC], + }); + expect(cursor.orderBy).toEqual([asc(table.lastName), desc(table.id)]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorDESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDESC], + }); + expect(cursor.orderBy).toEqual([desc(table.lastName), desc(table.id)]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.id, previousData.id) + ) + ) + ); + }); + }); + }); + + describe("with two cursors", () => { + const cursorDefault: Cursor = { + key: "lastName", + schema: table.lastName, + }; + + const cursorASC: Cursor = { + key: "lastName", + order: "ASC", + schema: table.lastName, + }; + + const cursorDESC: Cursor = { + key: "lastName", + order: "DESC", + schema: table.lastName, + }; + + const cursor2Default: Cursor = { + key: "firstName", + schema: table.firstName, + }; + + const cursor2ASC: Cursor = { + key: "firstName", + order: "ASC", + schema: table.firstName, + }; + + const cursor2DESC: Cursor = { + key: "firstName", + order: "DESC", + schema: table.firstName, + }; + + describe("without previous data generates only orderBy and where is undefined", () => { + test("with primaryCursorDefault and cursorDefault and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDefault, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorDefault and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDefault, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorDefault and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDefault, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorASC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorASC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorASC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorASC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorASC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorASC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorDESC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDESC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorDESC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDESC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDefault and cursorDESC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDESC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorDefault and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDefault, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorDefault and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDefault, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorDefault and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDefault, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorASC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorASC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorASC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorASC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorASC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorASC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorDESC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDESC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorDESC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDESC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorASC and cursorDESC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDESC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorDefault and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDefault, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorDefault and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDefault, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorDefault and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDefault, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorASC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorASC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorASC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorASC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorASC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorASC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorDESC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDESC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorDESC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDESC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with primaryCursorDESC and cursorDESC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDESC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + desc(table.firstName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + }); + + describe("with previous data generates orderBy and where", () => { + const previousData = { + id: 1, + firstName: "John", + middleName: "Doe", + lastName: "Smith", + phone: "123456789", + email: "johndoe", + }; + + test("with primaryCursorDefault and cursorDefault and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDefault, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorDefault and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDefault, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorDefault and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDefault, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorASC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorASC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorASC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorASC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorASC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorASC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorDESC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDESC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorDESC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDESC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDefault and cursorDESC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDefault, + cursors: [cursorDESC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorDefault and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDefault, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorDefault and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDefault, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorDefault and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDefault, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorASC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorASC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorASC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorASC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorASC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorASC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorDESC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDESC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorDESC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDESC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorASC and cursorDESC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorASC, + cursors: [cursorDESC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + desc(table.firstName), + asc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + gt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorDefault and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDefault, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorDefault and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDefault, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorDefault and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDefault, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + desc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorASC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorASC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorASC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorASC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorASC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorASC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + asc(table.lastName), + desc(table.firstName), + desc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(gt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorDESC and cursor2Default", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDESC, cursor2Default], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorDESC and cursor2ASC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDESC, cursor2ASC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + asc(table.firstName), + desc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + gt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + lt(table.id, previousData.id) + ) + ) + ); + }); + + test("with primaryCursorDESC and cursorDESC and cursor2DESC", () => { + const cursor = generateCursor({ + primaryCursor: primaryCursorDESC, + cursors: [cursorDESC, cursor2DESC], + }); + expect(cursor.orderBy).toEqual([ + desc(table.lastName), + desc(table.firstName), + desc(table.id), + ]); + expect(cursor.where(previousData)).toEqual( + or( + and(lt(table.lastName, previousData.lastName)), + and( + eq(table.lastName, previousData.lastName), + lt(table.firstName, previousData.firstName) + ), + and( + eq(table.lastName, previousData.lastName), + eq(table.firstName, previousData.firstName), + lt(table.id, previousData.id) + ) + ) + ); + }); + }); + }); + + describe("with multiple cursors", () => { + test("with three cursors and no previous data", () => { + const cursor = generateCursor({ + primaryCursor: { key: "id", schema: table.id, order: "DESC" }, + cursors: [ + { key: "firstName", schema: table.firstName }, + { key: "lastName", schema: table.lastName, order: "DESC" }, + { key: "middleName", schema: table.middleName }, + ], + }); + expect(cursor.orderBy).toEqual([ + asc(table.firstName), + desc(table.lastName), + asc(table.middleName), + desc(table.id), + ]); + expect(cursor.where()).toBeUndefined(); + }); + + test("with three cursors and previous data", () => { + const item = { + id: 1, + firstName: "John", + middleName: "Doe", + lastName: "Smith", + phone: "123456789", + email: "johndoe", + }; + + const cursor = generateCursor({ + cursors: [ + { key: "firstName", schema: table.firstName, order: "DESC" }, + { key: "lastName", schema: table.lastName }, + { key: "middleName", schema: table.middleName, order: "DESC" }, + ], + primaryCursor: { key: "id", schema: table.id, order: "ASC" }, + }); + + expect(cursor.orderBy).toEqual([ + desc(table.firstName), + asc(table.lastName), + desc(table.middleName), + asc(table.id), + ]); + expect(cursor.where(item)).toEqual( + or( + and(lt(table.firstName, item.firstName)), + and( + eq(table.firstName, item.firstName), + gt(table.lastName, item.lastName) + ), + and( + eq(table.firstName, item.firstName), + eq(table.lastName, item.lastName), + lt(table.middleName, item.middleName) + ), + and( + eq(table.firstName, item.firstName), + eq(table.lastName, item.lastName), + eq(table.middleName, item.middleName), + gt(table.id, item.id) + ) + ) + ); + }); + }); +}); diff --git a/test/index.test.ts b/test/index.test.ts deleted file mode 100644 index fe7b978..0000000 --- a/test/index.test.ts +++ /dev/null @@ -1,1701 +0,0 @@ -import { Cursor, generateCursor } from "../src"; -import { and, asc, desc, eq, gt, lt, or } from "drizzle-orm"; -import { describe, expect, test } from "vitest"; -import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; - -import { e } from "vitest/dist/reporters-cb94c88b"; - -const table = sqliteTable("users", { - id: integer("id").primaryKey({ autoIncrement: true }), - firstName: text("first_name"), - middleName: text("middle_name"), - lastName: text("last_name"), - phone: text("phone"), - email: text("email"), -}); - -describe("index", () => { - describe("generateCursor", () => { - const primaryCursorDefault: Cursor = { key: "id", schema: table.id }; - const primaryCursorASC: Cursor = { - key: "id", - order: "ASC", - schema: table.id, - }; - const primaryCursorDESC: Cursor = { - key: "id", - order: "DESC", - schema: table.id, - }; - - describe("with only primaryCursor", () => { - describe("without previous data generates only orderBy and where is undefined", () => { - test("with primaryCursorDefault", () => { - expect( - generateCursor({ primaryCursor: primaryCursorDefault }) - ).to.deep.equal({ - orderBy: [asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorASC", () => { - expect( - generateCursor({ primaryCursor: primaryCursorASC }) - ).to.deep.equal({ - orderBy: [asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDESC", () => { - expect( - generateCursor({ primaryCursor: primaryCursorDESC }) - ).to.deep.equal({ - orderBy: [desc(table.id)], - where: undefined, - }); - }); - }); - describe("with previous data generates orderBy and where", () => { - const previousData = { - id: 1, - firstName: "John", - middleName: "Doe", - lastName: "Smith", - phone: "123456789", - email: "johndoe", - }; - - test("with primaryCursorDefault", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorDefault }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.id)], - where: and(or(gt(table.id, previousData.id))), - }); - }); - - test("with primaryCursorASC", () => { - expect( - generateCursor({ primaryCursor: primaryCursorASC }, previousData) - ).to.deep.equal({ - orderBy: [asc(table.id)], - where: and(or(gt(table.id, previousData.id))), - }); - }); - - test("with primaryCursorDESC", () => { - expect( - generateCursor({ primaryCursor: primaryCursorDESC }, previousData) - ).to.deep.equal({ - orderBy: [desc(table.id)], - where: and(or(lt(table.id, previousData.id))), - }); - }); - }); - }); - - describe("with one cursor", () => { - const cursorDefault: Cursor = { - key: "lastName", - schema: table.lastName, - }; - - const cursorASC: Cursor = { - key: "lastName", - order: "ASC", - schema: table.lastName, - }; - - const cursorDESC: Cursor = { - key: "lastName", - order: "DESC", - schema: table.lastName, - }; - - describe("without previous data generates only orderBy and where is undefined", () => { - test("with primaryCursorDefault and cursorDefault", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorDefault], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorASC], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorDESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorDESC], - }) - ).to.deep.equal({ - orderBy: [desc(table.lastName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorDefault", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorDefault], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorASC], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorDESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorDESC], - }) - ).to.deep.equal({ - orderBy: [desc(table.lastName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorDefault", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorDefault], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), desc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorASC], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), desc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorDESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorDESC], - }) - ).to.deep.equal({ - orderBy: [desc(table.lastName), desc(table.id)], - where: undefined, - }); - }); - }); - - describe("with previous data generates orderBy and where", () => { - const previousData = { - id: 1, - firstName: "John", - middleName: "Doe", - lastName: "Smith", - phone: "123456789", - email: "johndoe", - }; - - test("with primaryCursorDefault and cursorDefault", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorDefault, cursors: [cursorDefault] }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorASC", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorDefault, cursors: [cursorASC] }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorDESC", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorDefault, cursors: [cursorDESC] }, - previousData - ) - ).to.deep.equal({ - orderBy: [desc(table.lastName), asc(table.id)], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorDefault", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorASC, cursors: [cursorDefault] }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorASC", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorASC, cursors: [cursorASC] }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorDESC", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorASC, cursors: [cursorDESC] }, - previousData - ) - ).to.deep.equal({ - orderBy: [desc(table.lastName), asc(table.id)], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorDefault", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorDESC, cursors: [cursorDefault] }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), desc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorASC", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorDESC, cursors: [cursorASC] }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), desc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorDESC", () => { - expect( - generateCursor( - { primaryCursor: primaryCursorDESC, cursors: [cursorDESC] }, - previousData - ) - ).to.deep.equal({ - orderBy: [desc(table.lastName), desc(table.id)], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - //----------------- - }); - }); - - describe("with two cursors", () => { - const cursorDefault: Cursor = { - key: "lastName", - schema: table.lastName, - }; - - const cursorASC: Cursor = { - key: "lastName", - order: "ASC", - schema: table.lastName, - }; - - const cursorDESC: Cursor = { - key: "lastName", - order: "DESC", - schema: table.lastName, - }; - - const cursor2Default: Cursor = { - key: "firstName", - schema: table.firstName, - }; - - const cursor2ASC: Cursor = { - key: "firstName", - order: "ASC", - schema: table.firstName, - }; - - const cursor2DESC: Cursor = { - key: "firstName", - order: "DESC", - schema: table.firstName, - }; - - describe("without previous data generates only orderBy and where is undefined", () => { - test("with primaryCursorDefault and cursorDefault and cursor2Default", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorDefault, cursor2Default], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorDefault and cursor2ASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorDefault, cursor2ASC], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorDefault and cursor2DESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorDefault, cursor2DESC], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorASC and cursor2Default", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorASC, cursor2Default], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorASC and cursor2ASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorASC, cursor2ASC], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorASC and cursor2DESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorASC, cursor2DESC], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorDESC and cursor2Default", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorDESC, cursor2Default], - }) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorDESC and cursor2ASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorDESC, cursor2ASC], - }) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDefault and cursorDESC and cursor2DESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDefault, - cursors: [cursorDESC, cursor2DESC], - }) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorDefault and cursor2Default", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorDefault, cursor2Default], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorDefault and cursor2ASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorDefault, cursor2ASC], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorDefault and cursor2DESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorDefault, cursor2DESC], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorASC and cursor2Default", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorASC, cursor2Default], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorASC and cursor2ASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorASC, cursor2ASC], - }) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorASC and cursor2DESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorASC, cursor2DESC], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorDESC and cursor2Default", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorDESC, cursor2Default], - }) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorDESC and cursor2ASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorDESC, cursor2ASC], - }) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorASC and cursorDESC and cursor2DESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorASC, - cursors: [cursorDESC, cursor2DESC], - }) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorDefault and cursor2Default", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorDefault, cursor2Default], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorDefault and cursor2ASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorDefault, cursor2ASC], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorDefault and cursor2DESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorDefault, cursor2DESC], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - desc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorASC and cursor2Default", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorASC, cursor2Default], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorASC and cursor2ASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorASC, cursor2ASC], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorASC and cursor2DESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorASC, cursor2DESC], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - desc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorDESC and cursor2Default", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorDESC, cursor2Default], - }) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorDESC and cursor2ASC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorDESC, cursor2ASC], - }) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: undefined, - }); - }); - - test("with primaryCursorDESC and cursorDESC and cursor2DESC", () => { - expect( - generateCursor({ - primaryCursor: primaryCursorDESC, - cursors: [cursorDESC, cursor2DESC], - }) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - desc(table.firstName), - desc(table.id), - ], - where: undefined, - }); - }); - }); - - describe("with previous data generates orderBy and where", () => { - const previousData = { - id: 1, - firstName: "John", - middleName: "Doe", - lastName: "Smith", - phone: "123456789", - email: "johndoe", - }; - - test("with primaryCursorDefault and cursorDefault and cursor2Default", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDefault, - cursors: [cursorDefault, cursor2Default], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorDefault and cursor2ASC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDefault, - cursors: [cursorDefault, cursor2ASC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorDefault and cursor2DESC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDefault, - cursors: [cursorDefault, cursor2DESC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorASC and cursor2Default", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDefault, - cursors: [cursorASC, cursor2Default], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorASC and cursor2ASC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDefault, - cursors: [cursorASC, cursor2ASC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorASC and cursor2DESC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDefault, - cursors: [cursorASC, cursor2DESC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorDESC and cursor2Default", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDefault, - cursors: [cursorDESC, cursor2Default], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - asc(table.id), - ], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorDESC and cursor2ASC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDefault, - cursors: [cursorDESC, cursor2ASC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - asc(table.id), - ], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDefault and cursorDESC and cursor2DESC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDefault, - cursors: [cursorDESC, cursor2DESC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorDefault and cursor2Default", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorASC, - cursors: [cursorDefault, cursor2Default], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorDefault and cursor2ASC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorASC, - cursors: [cursorDefault, cursor2ASC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorDefault and cursor2DESC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorASC, - cursors: [cursorDefault, cursor2DESC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorASC and cursor2Default", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorASC, - cursors: [cursorASC, cursor2Default], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorASC and cursor2ASC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorASC, - cursors: [cursorASC, cursor2ASC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [asc(table.lastName), asc(table.firstName), asc(table.id)], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorASC and cursor2DESC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorASC, - cursors: [cursorASC, cursor2DESC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorDESC and cursor2Default", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorASC, - cursors: [cursorDESC, cursor2Default], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - asc(table.id), - ], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorDESC and cursor2ASC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorASC, - cursors: [cursorDESC, cursor2ASC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - asc(table.id), - ], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorASC and cursorDESC and cursor2DESC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorASC, - cursors: [cursorDESC, cursor2DESC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - desc(table.firstName), - asc(table.id), - ], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - gt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorDefault and cursor2Default", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDESC, - cursors: [cursorDefault, cursor2Default], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorDefault and cursor2ASC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDESC, - cursors: [cursorDefault, cursor2ASC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorDefault and cursor2DESC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDESC, - cursors: [cursorDefault, cursor2DESC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - desc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorASC and cursor2Default", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDESC, - cursors: [cursorASC, cursor2Default], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorASC and cursor2ASC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDESC, - cursors: [cursorASC, cursor2ASC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorASC and cursor2DESC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDESC, - cursors: [cursorASC, cursor2DESC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - asc(table.lastName), - desc(table.firstName), - desc(table.id), - ], - where: or( - and(gt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorDESC and cursor2Default", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDESC, - cursors: [cursorDESC, cursor2Default], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorDESC and cursor2ASC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDESC, - cursors: [cursorDESC, cursor2ASC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - asc(table.firstName), - desc(table.id), - ], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - gt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - lt(table.id, previousData.id) - ) - ), - }); - }); - - test("with primaryCursorDESC and cursorDESC and cursor2DESC", () => { - expect( - generateCursor( - { - primaryCursor: primaryCursorDESC, - cursors: [cursorDESC, cursor2DESC], - }, - previousData - ) - ).to.deep.equal({ - orderBy: [ - desc(table.lastName), - desc(table.firstName), - desc(table.id), - ], - where: or( - and(lt(table.lastName, previousData.lastName)), - and( - eq(table.lastName, previousData.lastName), - lt(table.firstName, previousData.firstName) - ), - and( - eq(table.lastName, previousData.lastName), - eq(table.firstName, previousData.firstName), - lt(table.id, previousData.id) - ) - ), - }); - }); - }); - }); - - describe("with multiple cursors", () => { - test("with three cursors and no previous data", () => { - expect( - generateCursor({ - primaryCursor: { key: "id", schema: table.id, order: "DESC" }, - cursors: [ - { key: "firstName", schema: table.firstName }, - { key: "lastName", schema: table.lastName, order: "DESC" }, - { key: "middleName", schema: table.middleName }, - ], - }) - ).to.deep.equal({ - orderBy: [ - asc(table.firstName), - desc(table.lastName), - asc(table.middleName), - desc(table.id), - ], - where: undefined, - }); - }); - - test("with three cursors and previous data", () => { - expect( - generateCursor( - { - cursors: [ - { key: "firstName", schema: table.firstName, order: "DESC" }, - { key: "lastName", schema: table.lastName }, - { key: "middleName", schema: table.middleName, order: "DESC" }, - ], - primaryCursor: { key: "id", schema: table.id, order: "ASC" }, - }, - { - id: 1, - firstName: "John", - middleName: "Doe", - lastName: "Smith", - phone: "123456789", - email: "johndoe", - } - ) - ).to.deep.equal({ - orderBy: [ - desc(table.firstName), - asc(table.lastName), - desc(table.middleName), - asc(table.id), - ], - where: or( - and(lt(table.firstName, "John")), - and(eq(table.firstName, "John"), gt(table.lastName, "Smith")), - and( - eq(table.firstName, "John"), - eq(table.lastName, "Smith"), - lt(table.middleName, "Doe") - ), - and( - eq(table.firstName, "John"), - eq(table.lastName, "Smith"), - eq(table.middleName, "Doe"), - gt(table.id, 1) - ) - ), - }); - }); - }); - }); -}); diff --git a/test/parse.test.ts b/test/parse.test.ts new file mode 100644 index 0000000..dfb1426 --- /dev/null +++ b/test/parse.test.ts @@ -0,0 +1,125 @@ +import { CursorConfig, generateCursor, parse, serialize } from "../src"; +import { and, asc, desc, eq, gt, lt, or } from "drizzle-orm"; +import { describe, expect, test } from "vitest"; +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +describe("cursors", () => { + const table = sqliteTable("users", { + id: integer("id").primaryKey({ autoIncrement: true }), + firstName: text("first_name"), + middleName: text("middle_name"), + lastName: text("last_name"), + phone: text("phone"), + email: text("email"), + }); + + const config: CursorConfig = { + cursors: [ + { key: "firstName", schema: table.firstName, order: "DESC" }, + { key: "lastName", schema: table.lastName }, + { key: "middleName", schema: table.middleName, order: "DESC" }, + ], + primaryCursor: { key: "id", schema: table.id, order: "ASC" }, + }; + + const data = { + id: 1, + firstName: "John", + middleName: "Doe", + lastName: "Smith", + phone: "1234567890", + email: "john@doe.com", + }; + + const cursorToken = + "eyJpZCI6MSwiZmlyc3ROYW1lIjoiSm9obiIsImxhc3ROYW1lIjoiU21pdGgiLCJtaWRkbGVOYW1lIjoiRG9lIn0="; + + describe("parse", () => { + test("parses a cursor", () => { + expect(parse(config, cursorToken)).to.deep.equal({ + // Not email or phono in the cursor + id: data.id, + firstName: data.firstName, + middleName: data.middleName, + lastName: data.lastName, + }); + }); + }); + + describe("parse & serialize", () => { + test("generates the same cursor", () => { + expect(serialize(config, parse(config, cursorToken))).to.equal( + cursorToken + ); + }); + }); + + describe("parse & generateCursor", () => { + test("generates cursor with parse", () => { + const cursor = generateCursor(config); + expect(cursor.orderBy).toEqual([ + desc(table.firstName), + asc(table.lastName), + desc(table.middleName), + asc(table.id), + ]); + + expect(cursor.where(data)).toEqual( + or( + and(lt(table.firstName, data.firstName)), + and( + eq(table.firstName, data.firstName), + gt(table.lastName, data.lastName) + ), + and( + eq(table.firstName, data.firstName), + eq(table.lastName, data.lastName), + lt(table.middleName, data.middleName) + ), + and( + eq(table.firstName, data.firstName), + eq(table.lastName, data.lastName), + eq(table.middleName, data.middleName), + gt(table.id, data.id) + ) + ) + ); + }); + + test("generates cursor with token directly", () => { + const _cursor = generateCursor(config); + expect(_cursor.orderBy).toEqual([ + desc(table.firstName), + asc(table.lastName), + desc(table.middleName), + asc(table.id), + ]); + + expect(_cursor.where()).toEqual(undefined); + expect(_cursor.where(cursorToken)).toEqual( + or( + and(lt(table.firstName, "John")), + and(eq(table.firstName, "John"), gt(table.lastName, "Smith")), + and( + eq(table.firstName, "John"), + eq(table.lastName, "Smith"), + lt(table.middleName, "Doe") + ), + and( + eq(table.firstName, "John"), + eq(table.lastName, "Smith"), + eq(table.middleName, "Doe"), + gt(table.id, 1) + ) + ) + ); + }); + + test("where with token or data object should generate the same cursor", () => { + const cursor = generateCursor(config); + expect(cursor.where(data)).toEqual(cursor.where(serialize(config, data))); + expect(cursor.where(data)).toEqual(cursor.where(cursor.serialize(data))); + expect(cursor.where(data)).toEqual(cursor.where(cursorToken)); + }); + }); +}); diff --git a/test/serialize.test.ts b/test/serialize.test.ts new file mode 100644 index 0000000..48f0482 --- /dev/null +++ b/test/serialize.test.ts @@ -0,0 +1,50 @@ +import { CursorConfig, generateCursor, parse, serialize } from "../src"; +import { and, asc, desc, eq, gt, lt, or } from "drizzle-orm"; +import { describe, expect, test } from "vitest"; +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +describe("cursors", () => { + const table = sqliteTable("users", { + id: integer("id").primaryKey({ autoIncrement: true }), + firstName: text("first_name"), + middleName: text("middle_name"), + lastName: text("last_name"), + phone: text("phone"), + email: text("email"), + }); + + const config: CursorConfig = { + cursors: [ + { key: "firstName", schema: table.firstName, order: "DESC" }, + { key: "lastName", schema: table.lastName }, + { key: "middleName", schema: table.middleName, order: "DESC" }, + ], + primaryCursor: { key: "id", schema: table.id, order: "ASC" }, + }; + + const data = { + id: 1, + firstName: "John", + middleName: "Doe", + lastName: "Smith", + phone: "1234567890", + email: "john@doe.com", + }; + + const cursorToken = + "eyJpZCI6MSwiZmlyc3ROYW1lIjoiSm9obiIsImxhc3ROYW1lIjoiU21pdGgiLCJtaWRkbGVOYW1lIjoiRG9lIn0="; + + describe("serialize", () => { + test("generates a cursor", () => { + expect(serialize(config, data)).to.equal(cursorToken); + }); + }); + + describe("parse & serialize", () => { + test("generates the same cursor", () => { + expect(serialize(config, parse(config, cursorToken))).to.equal( + cursorToken + ); + }); + }); +});