Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added typescript, replaced babel by swc and added support for column alias #1

Merged
merged 4 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions babel.config.js

This file was deleted.

File renamed without changes.
2 changes: 1 addition & 1 deletion fixup
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ cat >dist/esm/package.json <<!EOF
{
"type": "module"
}
!EOF
!EOF
6 changes: 5 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
coverageDirectory: 'coverage',
globalSetup: './src/testingSetup.js',
globalSetup: './src/testingSetup.ts',
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest',
},
}
9,904 changes: 5,485 additions & 4,419 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dashdot/cursors",
"version": "0.3.0",
"version": "0.4.0",
"description": "A simple SQL and Mongo implementation of cursor based pagination",
"license": "MIT",
"files": [
Expand All @@ -15,31 +15,33 @@
},
"scripts": {
"lint": "eslint .",
"test": "jest",
"test:coverage": "jest --coverage",
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
"benchmark": "node -r esm benchmark.js",
"codecov": "codecov",
"build": "rm -rf dist/* && tsc -p tsconfig-esm.json && tsc -p tsconfig-cjs.json && ./fixup",
"prepare": "npm run build"
},
"dependencies": {},
"devDependencies": {
"@babel/plugin-transform-runtime": "^7.16.10",
"@babel/preset-env": "^7.16.11",
"@dashdot/eslint-config-base": "^0.3.0",
"@dashdot/factory": "^0.1.4",
"@faker-js/faker": "^6.0.0-alpha.5",
"@dashdot/factory": "^0.2.2",
"@faker-js/faker": "^6.3.1",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.7",
"@types/pg": "^8.11.5",
"benchmark": "^2.1.4",
"dotenv": "^16.0.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.0.0",
"esm": "^3.2.25",
"jest": "^27.4.7",
"jest": "^29.7.0",
"knex": "^1.0.2",
"pg": "^8.7.3",
"pg": "^8.11.5",
"thenby": "^1.3.4",
"ts-jest": "^29.1.2",
"typescript": "^4.5.5"
},
"eslintConfig": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,19 @@ test('if \'cursorToSqlQuery\' return correct values when no cursor is provided.'
]
expect(query).toEqual(expectedResult)
})

test('if \'cursorToSqlQuery\' return correct values using joined column.', async () => {
const taskId = 1
const userId = 5
const taskName = 'task'
const joinedTaskUser = { taskId, userId, 'tasks.name': taskName }
const orderBy = { 'tasks.name': ASC}
const cursor = createCursor(joinedTaskUser, orderBy)
const query = cursorToSqlQuery(cursor, orderBy)
const expectedResult = [
'"tasks.name" > $1',
[taskName],
'"tasks.name" ASC',
]
expect(query).toEqual(expectedResult)
})
120 changes: 83 additions & 37 deletions src/__tests__/postgres.test.js → src/__tests__/postgres.test.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,30 @@
/* eslint-disable no-await-in-loop */
import by from 'thenby'
import faker from '@faker-js/faker'
import by from 'thenby'
import { Knex } from 'knex'
import {
ASC,
DESC,
createCursor,
cursorToSqlQuery,
ASC, DESC, createCursor, cursorToSqlQuery
} from '../index'
import {
createTestDatabase,
destroyTestDatabase,
KnexFactory,
getKnexConnection
getKnexConnection,
} from '../testing'
import UserFactory from '../factories/UserFactory'
import { TABLE_TASKS, TASK_STATUS_COMPLETED, TASK_STATUS_PENDING, tasks } from '../factories/TaskFactory'
import faker from '@faker-js/faker'

const TABLE_USERS = 'users'
const TOTAL_USERS = 10
const DATABASE_NAME = 'testing-postgres'
const TOTAL_USERS = 2
const TASKS_PER_USER = 2

// We use a random word so when the tests run in parallel they don't collide.
const DATABASE_NAME = faker.random.word()

const users = () => {
const knex = getKnexConnection()
const knex = getKnexConnection(DATABASE_NAME)
return knex(TABLE_USERS)
}

class UserFactory extends KnexFactory {
static get table() {
return users
}

static async make() {
return {
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
email: faker.unique(faker.internet.email),
}
}
}

const createSchema = async (knex) => {
const createSchema = async (knex: Knex | null) => {
if (knex == null) {
return
}
Expand All @@ -49,16 +36,26 @@ const createSchema = async (knex) => {
table.string('email').notNullable()
table.string('firstName')
table.string('lastName')
table.unique('email')
table.unique(['email'])
})
await knex.schema.createTable(TABLE_TASKS, (table) => {
table.increments().primary()
table.datetime('createdAt').defaultTo(now)
table.datetime('updatedAt').defaultTo(now)
table.string('name').notNullable()
table.enum('status', [TASK_STATUS_PENDING, TASK_STATUS_COMPLETED])
table.integer('userId').unsigned().notNullable()
table.foreign('userId').references(`${TABLE_USERS}.id`)
})
}

let knex = null
let knex = null as Knex | null

beforeAll(async () => {
knex = await createTestDatabase(DATABASE_NAME)
await createSchema(knex)
await UserFactory.create(TOTAL_USERS)
const users = await UserFactory.create(TOTAL_USERS)
await Promise.all(users.map(async (user) => await user.createTasks(TASKS_PER_USER)))
})

afterAll(async () => {
Expand All @@ -67,7 +64,7 @@ afterAll(async () => {

test('if simple ascending order by finds the correct users.', async () => {
const allUsers = await users().where({})
const sortedUsers = allUsers.sort(by('id', 'asc'))
const sortedUsers = allUsers.sort(by.firstBy('id', 'asc'))
const orderBy = {
id: ASC,
}
Expand All @@ -89,7 +86,7 @@ test('if simple ascending order by finds the correct users.', async () => {

test('if simple descending order by finds the correct users.', async () => {
const allUsers = await users().where({})
const sortedUsers = allUsers.sort(by('id', 'desc'))
const sortedUsers = allUsers.sort(by.firstBy('id', 'desc'))
const orderBy = {
id: DESC,
}
Expand All @@ -112,9 +109,7 @@ test('if simple descending order by finds the correct users.', async () => {
test('if advanced order by finds the correct users.', async () => {
const allUsers = await users().where({})
const sortedUsers = allUsers.sort(
by('lastName', 'desc')
.thenBy('firstName', 'asc')
.thenBy('id', 'desc')
by.firstBy('lastName', 'desc').thenBy('firstName', 'asc').thenBy('id', 'desc')
)
const orderBy = {
lastName: DESC,
Expand All @@ -139,12 +134,12 @@ test('if advanced order by finds the correct users.', async () => {

test('if no cursor sorts users correctly.', async () => {
const allUsers = await users().where({})
const sortedUsers = allUsers.sort(by('id', 'desc'))
const sortedUsers = allUsers.sort(by.firstBy('id', 'desc'))
const orderBy = {
id: DESC,
}
let nextUser = sortedUsers[0]
let cursor = null
let cursor: string | null = null
for (let i = 0; i <= TOTAL_USERS; i += 1) {
const [query, bindings, orderQuery] = cursorToSqlQuery(cursor, orderBy)
const knexQuery = query.replace(/\$\w+/g, '?')
Expand All @@ -159,3 +154,54 @@ test('if no cursor sorts users correctly.', async () => {
}
}
})

test('If joined table is sorted correctly', async () => {
const allTasks = await tasks(DATABASE_NAME)
.select(
`${TABLE_TASKS}.id as taskId`,
`${TABLE_TASKS}.createdAt`,
`${TABLE_TASKS}.name`,
`${TABLE_USERS}.id as userId`,
)
.leftJoin(TABLE_USERS, `${TABLE_TASKS}.userId`, `${TABLE_USERS}.id`)
const sortedTasks = allTasks.sort(by.firstBy('createdAt', 'asc').thenBy('userId', 'asc'))

const TOTAL_USER_TASKS = TOTAL_USERS * TASKS_PER_USER
expect(sortedTasks.length).toEqual(TOTAL_USER_TASKS)

const orderBy = {
createdAt: {
direction: ASC,
column: `${TABLE_TASKS}"."createdAt`,
},
userId: {
direction: ASC,
column: `${TABLE_USERS}"."id`,
},
}

let cursor: string | null = null
let nextTask = sortedTasks[0]
for (let i = 0; i <= TOTAL_USER_TASKS; i += 1) {
const [whereQuery, whereValues, orderQuery] = cursorToSqlQuery(cursor, orderBy)
const knexQuery = whereQuery.replace(/\$\w+/g, '?')
nextTask = await tasks()
.select(
`${TABLE_TASKS}.id as taskId`,
`${TABLE_TASKS}.createdAt`,
`${TABLE_TASKS}.name`,
`${TABLE_USERS}.id as userId`,
)
.leftJoin(TABLE_USERS, `${TABLE_TASKS}.userId`, `${TABLE_USERS}.id`)
.whereRaw(knexQuery, whereValues)
.orderByRaw(orderQuery)
.first()
if (i < TOTAL_USER_TASKS) {
expect(nextTask).toBeDefined()
expect(nextTask.name).toEqual(sortedTasks[i].name)
expect(nextTask.taskId).toEqual(sortedTasks[i].taskId)
expect(nextTask.createdAt).toEqual(sortedTasks[i].createdAt)
cursor = createCursor(nextTask, orderBy)
}
}
})
50 changes: 0 additions & 50 deletions src/benchmarking.js

This file was deleted.

29 changes: 29 additions & 0 deletions src/benchmarking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Knex } from 'knex'
import {
createTestDatabase,
destroyTestDatabase,
} from './testing'
import UserFactory, { TABLE_USERS } from 'factories/UserFactory'

const TOTAL_USERS = 2000

export const createBenchmarkDatabase = async (databaseName: string) => {
const knex = await createTestDatabase(databaseName)
if (knex) {
const now = knex.fn.now()
await knex.schema.createTable(TABLE_USERS, (table) => {
table.increments().primary()
table.datetime('createdAt').defaultTo(now)
table.datetime('updatedAt').defaultTo(now)
table.string('email').notNullable()
table.string('firstName')
table.string('lastName')
table.unique(['email'])
})
await UserFactory.create(TOTAL_USERS)
}
}

export const destroyBenchmarkDatabase = async (knex: Knex) => {
await destroyTestDatabase(knex)
}
26 changes: 26 additions & 0 deletions src/factories/TaskFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import faker from '@faker-js/faker'
import { KnexFactory, getKnexConnection } from '../testing'

export const TABLE_TASKS = 'tasks'

export const TASK_STATUS_PENDING = 'pending'
export const TASK_STATUS_COMPLETED = 'completed'

export const tasks = (databaseName?: string) => {
const knex = getKnexConnection(databaseName)
return knex(TABLE_TASKS)
}

export default class TaskFactory extends KnexFactory {
static get table() {
return tasks
}

static async make() {
return {
name: faker.unique(faker.name.jobTitle),
status: faker.helpers.arrayElement([TASK_STATUS_PENDING, TASK_STATUS_COMPLETED]),
createdAt: faker.date.past(),
}
}
}
Loading
Loading