fix: clean up

This commit is contained in:
ppetzold 2023-03-14 22:10:23 +01:00
parent 2a628e89af
commit d7ccd914d4
6 changed files with 105 additions and 111 deletions

View File

@ -1,3 +1,2 @@
export * from './decorator' export * from './decorator'
export * from './paginate' export * from './paginate'
export { FilterOperator, FilterComparator } from './operator'

View File

@ -1,65 +0,0 @@
import { values } from 'lodash'
import {
Equal,
FindOperator,
In,
MoreThan,
MoreThanOrEqual,
IsNull,
LessThan,
LessThanOrEqual,
Between,
ILike,
Not,
} from 'typeorm'
export enum FilterOperator {
EQ = '$eq',
GT = '$gt',
GTE = '$gte',
IN = '$in',
NULL = '$null',
LT = '$lt',
LTE = '$lte',
BTW = '$btw',
ILIKE = '$ilike',
SW = '$sw',
}
export function isOperator(value: unknown): value is FilterOperator {
return values(FilterOperator).includes(value as any)
}
export enum FilterSuffix {
NOT = '$not',
}
export function isSuffix(value: unknown): value is FilterSuffix {
return values(FilterSuffix).includes(value as any)
}
export enum FilterComparator {
AND = '$and',
OR = '$or',
}
export function isComparator(value: unknown): value is FilterComparator {
return values(FilterComparator).includes(value as any)
}
export const OperatorSymbolToFunction = new Map<
FilterOperator | FilterSuffix,
(...args: any[]) => FindOperator<string>
>([
[FilterOperator.EQ, Equal],
[FilterOperator.GT, MoreThan],
[FilterOperator.GTE, MoreThanOrEqual],
[FilterOperator.IN, In],
[FilterOperator.NULL, IsNull],
[FilterOperator.LT, LessThan],
[FilterOperator.LTE, LessThanOrEqual],
[FilterOperator.BTW, Between],
[FilterOperator.ILIKE, ILike],
[FilterSuffix.NOT, Not],
[FilterOperator.SW, ILike],
])

View File

@ -1,6 +1,21 @@
import { Brackets, FindOperator, SelectQueryBuilder } from 'typeorm' import { values } from 'lodash'
import {
Brackets,
Equal,
FindOperator,
In,
MoreThan,
MoreThanOrEqual,
IsNull,
LessThan,
LessThanOrEqual,
Between,
ILike,
Not,
SelectQueryBuilder,
} from 'typeorm'
import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause' import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause'
import { PaginateQuery } from './decorator' import { PaginateQuery } from '../decorator'
import { import {
checkIsEmbedded, checkIsEmbedded,
checkIsRelation, checkIsRelation,
@ -8,16 +23,57 @@ import {
fixColumnAlias, fixColumnAlias,
getPropertiesByColumnName, getPropertiesByColumnName,
} from './helper' } from './helper'
import {
FilterComparator, export enum FilterOperator {
FilterOperator, EQ = '$eq',
FilterSuffix, GT = '$gt',
isComparator, GTE = '$gte',
isOperator, IN = '$in',
isSuffix, NULL = '$null',
OperatorSymbolToFunction, LT = '$lt',
} from './operator' LTE = '$lte',
import { PaginateConfig } from './paginate' BTW = '$btw',
ILIKE = '$ilike',
SW = '$sw',
}
export function isOperator(value: unknown): value is FilterOperator {
return values(FilterOperator).includes(value as any)
}
export enum FilterSuffix {
NOT = '$not',
}
export function isSuffix(value: unknown): value is FilterSuffix {
return values(FilterSuffix).includes(value as any)
}
export enum FilterComparator {
AND = '$and',
OR = '$or',
}
export function isComparator(value: unknown): value is FilterComparator {
return values(FilterComparator).includes(value as any)
}
export const OperatorSymbolToFunction = new Map<
FilterOperator | FilterSuffix,
(...args: any[]) => FindOperator<string>
>([
[FilterOperator.EQ, Equal],
[FilterOperator.GT, MoreThan],
[FilterOperator.GTE, MoreThanOrEqual],
[FilterOperator.IN, In],
[FilterOperator.NULL, IsNull],
[FilterOperator.LT, LessThan],
[FilterOperator.LTE, LessThanOrEqual],
[FilterOperator.BTW, Between],
[FilterOperator.ILIKE, ILike],
[FilterSuffix.NOT, Not],
[FilterOperator.SW, ILike],
])
type Filter = { comparator: FilterComparator; findOperator: FindOperator<string> } type Filter = { comparator: FilterComparator; findOperator: FindOperator<string> }
type ColumnsFilters = { [columnName: string]: Filter[] } type ColumnsFilters = { [columnName: string]: Filter[] }
@ -168,7 +224,7 @@ export function getFilterTokens(raw?: string): FilterToken | null {
export function parseFilter<T>( export function parseFilter<T>(
query: PaginateQuery, query: PaginateQuery,
filterableColumns?: PaginateConfig<T>['filterableColumns'] filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] }
): ColumnsFilters { ): ColumnsFilters {
const filter: ColumnsFilters = {} const filter: ColumnsFilters = {}
if (!filterableColumns || !query.filter) { if (!filterableColumns || !query.filter) {
@ -237,7 +293,7 @@ export function parseFilter<T>(
export function addFilter<T>( export function addFilter<T>(
qb: SelectQueryBuilder<T>, qb: SelectQueryBuilder<T>,
query: PaginateQuery, query: PaginateQuery,
filterableColumns?: PaginateConfig<T>['filterableColumns'] filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] }
): SelectQueryBuilder<T> { ): SelectQueryBuilder<T> {
const filter = parseFilter(query, filterableColumns) const filter = parseFilter(query, filterableColumns)
return qb.andWhere( return qb.andWhere(

View File

@ -34,6 +34,10 @@ export type RelationColumn<T> = Extract<
export type Order<T> = [Column<T>, 'ASC' | 'DESC'] export type Order<T> = [Column<T>, 'ASC' | 'DESC']
export type SortBy<T> = Order<T>[] export type SortBy<T> = Order<T>[]
export function isEntityKey<T>(entityColumns: Column<T>[], column: string): column is Column<T> {
return !!entityColumns.find((c) => c === column)
}
export const positiveNumberOrDefault = (value: number | undefined, defaultValue: number, minValue: 0 | 1 = 0) => export const positiveNumberOrDefault = (value: number | undefined, defaultValue: number, minValue: 0 | 1 = 0) =>
value === undefined || value < minValue ? defaultValue : value value === undefined || value < minValue ? defaultValue : value

View File

@ -1,21 +1,21 @@
import { Repository, In, DataSource, TypeORMError } from 'typeorm' import { Repository, In, DataSource, TypeORMError } from 'typeorm'
import { Paginated, paginate, PaginateConfig, NO_PAGINATION } from './paginate' import { Paginated, paginate, PaginateConfig, NO_PAGINATION } from '.'
import { PaginateQuery } from './decorator' import { PaginateQuery } from '../decorator'
import { HttpException } from '@nestjs/common' import { HttpException } from '@nestjs/common'
import { CatEntity } from './__tests__/cat.entity' import { CatEntity } from '../__tests__/cat.entity'
import { CatToyEntity } from './__tests__/cat-toy.entity' import { CatToyEntity } from '../__tests__/cat-toy.entity'
import { CatHomeEntity } from './__tests__/cat-home.entity' import { CatHomeEntity } from '../__tests__/cat-home.entity'
import { CatHomePillowEntity } from './__tests__/cat-home-pillow.entity' import { CatHomePillowEntity } from '../__tests__/cat-home-pillow.entity'
import { clone } from 'lodash' import { clone } from 'lodash'
import { import {
getFilterTokens,
FilterComparator, FilterComparator,
FilterOperator, FilterOperator,
FilterSuffix, FilterSuffix,
isOperator, isOperator,
isSuffix, isSuffix,
OperatorSymbolToFunction, OperatorSymbolToFunction,
} from './operator' } from './filter'
import { getFilterTokens } from './filter'
describe('paginate', () => { describe('paginate', () => {
let dataSource: DataSource let dataSource: DataSource

View File

@ -7,7 +7,7 @@ import {
ObjectLiteral, ObjectLiteral,
FindOptionsUtils, FindOptionsUtils,
} from 'typeorm' } from 'typeorm'
import { PaginateQuery } from './decorator' import { PaginateQuery } from '../decorator'
import { ServiceUnavailableException, Logger } from '@nestjs/common' import { ServiceUnavailableException, Logger } from '@nestjs/common'
import { mapKeys } from 'lodash' import { mapKeys } from 'lodash'
import { stringify } from 'querystring' import { stringify } from 'querystring'
@ -25,12 +25,14 @@ import {
SortBy, SortBy,
hasColumnWithPropertyPath, hasColumnWithPropertyPath,
includesAllPrimaryKeyColumns, includesAllPrimaryKeyColumns,
isEntityKey,
} from './helper' } from './helper'
import { FilterOperator, FilterSuffix } from './operator' import { addFilter, FilterOperator, FilterSuffix } from './filter'
import { addFilter } from './filter'
const logger: Logger = new Logger('nestjs-paginate') const logger: Logger = new Logger('nestjs-paginate')
export { FilterOperator, FilterSuffix }
export class Paginated<T> { export class Paginated<T> {
data: T[] data: T[]
meta: { meta: {
@ -121,10 +123,6 @@ export async function paginate<T extends ObjectLiteral>(
path = queryOrigin + queryPath path = queryOrigin + queryPath
} }
function isEntityKey(entityColumns: Column<T>[], column: string): column is Column<T> {
return !!entityColumns.find((c) => c === column)
}
if (config.sortableColumns.length < 1) { if (config.sortableColumns.length < 1) {
logger.debug("Missing required 'sortableColumns' config.") logger.debug("Missing required 'sortableColumns' config.")
throw new ServiceUnavailableException() throw new ServiceUnavailableException()
@ -142,18 +140,6 @@ export async function paginate<T extends ObjectLiteral>(
sortBy.push(...(config.defaultSortBy || [[config.sortableColumns[0], 'ASC']])) sortBy.push(...(config.defaultSortBy || [[config.sortableColumns[0], 'ASC']]))
} }
if (config.searchableColumns) {
if (query.searchBy) {
for (const column of query.searchBy) {
if (isEntityKey(config.searchableColumns, column)) {
searchBy.push(column)
}
}
} else {
searchBy.push(...config.searchableColumns)
}
}
let [items, totalItems]: [T[], number] = [[], 0] let [items, totalItems]: [T[], number] = [[], 0]
const queryBuilder = repo instanceof Repository ? repo.createQueryBuilder('__root') : repo const queryBuilder = repo instanceof Repository ? repo.createQueryBuilder('__root') : repo
@ -165,10 +151,12 @@ export async function paginate<T extends ObjectLiteral>(
} }
if (isPaginated) { if (isPaginated) {
// Switch from take and skip to limit and offset // Allow user to choose between limit/offset and take/skip.
// due to this problem https://github.com/typeorm/typeorm/issues/5670 // However, using limit/offset can return unexpected results.
// (anyway this creates more clean query without double distinct) // For more information see:
// queryBuilder.limit(limit).offset((page - 1) * limit) // [#477](https://github.com/ppetzold/nestjs-paginate/issues/477)
// [#4742](https://github.com/typeorm/typeorm/issues/4742)
// [#5670](https://github.com/typeorm/typeorm/issues/5670)
if (paginationType === PaginationType.LIMIT_AND_OFFSET) { if (paginationType === PaginationType.LIMIT_AND_OFFSET) {
queryBuilder.limit(limit).offset((page - 1) * limit) queryBuilder.limit(limit).offset((page - 1) * limit)
} else { } else {
@ -248,6 +236,18 @@ export async function paginate<T extends ObjectLiteral>(
queryBuilder.withDeleted() queryBuilder.withDeleted()
} }
if (config.searchableColumns) {
if (query.searchBy) {
for (const column of query.searchBy) {
if (isEntityKey(config.searchableColumns, column)) {
searchBy.push(column)
}
}
} else {
searchBy.push(...config.searchableColumns)
}
}
if (query.search && searchBy.length) { if (query.search && searchBy.length) {
queryBuilder.andWhere( queryBuilder.andWhere(
new Brackets((qb: SelectQueryBuilder<T>) => { new Brackets((qb: SelectQueryBuilder<T>) => {