fix: implemented a proper mechanism for using where condition in paginate options (#759)
This commit is contained in:
		
							parent
							
								
									e62feb869f
								
							
						
					
					
						commit
						a992f340c5
					
				@ -1,6 +1,7 @@
 | 
				
			|||||||
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
 | 
					import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
 | 
				
			||||||
import { CatEntity } from './cat.entity'
 | 
					import { CatEntity } from './cat.entity'
 | 
				
			||||||
import { SizeEmbed } from './size.embed'
 | 
					import { SizeEmbed } from './size.embed'
 | 
				
			||||||
 | 
					import { ToyShopEntity } from './toy-shop.entity'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Entity()
 | 
					@Entity()
 | 
				
			||||||
export class CatToyEntity {
 | 
					export class CatToyEntity {
 | 
				
			||||||
@ -13,6 +14,10 @@ export class CatToyEntity {
 | 
				
			|||||||
    @Column(() => SizeEmbed)
 | 
					    @Column(() => SizeEmbed)
 | 
				
			||||||
    size: SizeEmbed
 | 
					    size: SizeEmbed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ManyToOne(() => ToyShopEntity, { nullable: true })
 | 
				
			||||||
 | 
					    @JoinColumn()
 | 
				
			||||||
 | 
					    shop?: ToyShopEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @ManyToOne(() => CatEntity, (cat) => cat.toys)
 | 
					    @ManyToOne(() => CatEntity, (cat) => cat.toys)
 | 
				
			||||||
    @JoinColumn()
 | 
					    @JoinColumn()
 | 
				
			||||||
    cat: CatEntity
 | 
					    cat: CatEntity
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								src/__tests__/toy-shop-address.entity.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/__tests__/toy-shop-address.entity.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity()
 | 
				
			||||||
 | 
					export class ToyShopAddressEntity {
 | 
				
			||||||
 | 
					    @PrimaryGeneratedColumn()
 | 
				
			||||||
 | 
					    id: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Column()
 | 
				
			||||||
 | 
					    address: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @CreateDateColumn()
 | 
				
			||||||
 | 
					    createdAt: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/__tests__/toy-shop.entity.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/__tests__/toy-shop.entity.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import { Column, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm'
 | 
				
			||||||
 | 
					import { ToyShopAddressEntity } from './toy-shop-address.entity'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity()
 | 
				
			||||||
 | 
					export class ToyShopEntity {
 | 
				
			||||||
 | 
					    @PrimaryGeneratedColumn()
 | 
				
			||||||
 | 
					    id: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Column()
 | 
				
			||||||
 | 
					    shopName: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @OneToOne(() => ToyShopAddressEntity, { nullable: true })
 | 
				
			||||||
 | 
					    @JoinColumn()
 | 
				
			||||||
 | 
					    address: ToyShopAddressEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @CreateDateColumn()
 | 
				
			||||||
 | 
					    createdAt: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -140,8 +140,14 @@ export function fixColumnAlias(
 | 
				
			|||||||
            return `(${query(`${alias}_${properties.propertyPath}_rel`)})` // () is needed to avoid parameter conflict
 | 
					            return `(${query(`${alias}_${properties.propertyPath}_rel`)})` // () is needed to avoid parameter conflict
 | 
				
			||||||
        } else if ((isVirtualProperty && !query) || properties.isNested) {
 | 
					        } else if ((isVirtualProperty && !query) || properties.isNested) {
 | 
				
			||||||
            if (properties.propertyName.includes('.')) {
 | 
					            if (properties.propertyName.includes('.')) {
 | 
				
			||||||
                const [nestedRel, nestedCol] = properties.propertyName.split('.')
 | 
					                const propertyPath = properties.propertyName.split('.')
 | 
				
			||||||
                return `${alias}_${properties.propertyPath}_rel_${nestedRel}_rel.${nestedCol}`
 | 
					                const nestedRelations = propertyPath
 | 
				
			||||||
 | 
					                    .slice(0, -1)
 | 
				
			||||||
 | 
					                    .map((v) => `${v}_rel`)
 | 
				
			||||||
 | 
					                    .join('_')
 | 
				
			||||||
 | 
					                const nestedCol = propertyPath[propertyPath.length - 1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return `${alias}_${properties.propertyPath}_rel_${nestedRelations}.${nestedCol}`
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                return `${alias}_${properties.propertyPath}_rel_${properties.propertyName}`
 | 
					                return `${alias}_${properties.propertyPath}_rel_${properties.propertyName}`
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { Repository, In, DataSource, TypeORMError } from 'typeorm'
 | 
					import { Repository, In, DataSource, TypeORMError, Like } from 'typeorm'
 | 
				
			||||||
import { Paginated, paginate, PaginateConfig, NO_PAGINATION } from './paginate'
 | 
					import { Paginated, paginate, PaginateConfig, NO_PAGINATION } from './paginate'
 | 
				
			||||||
import { PaginateQuery } from './decorator'
 | 
					import { PaginateQuery } from './decorator'
 | 
				
			||||||
import { HttpException } from '@nestjs/common'
 | 
					import { HttpException } from '@nestjs/common'
 | 
				
			||||||
@ -16,6 +16,8 @@ import {
 | 
				
			|||||||
    isSuffix,
 | 
					    isSuffix,
 | 
				
			||||||
    OperatorSymbolToFunction,
 | 
					    OperatorSymbolToFunction,
 | 
				
			||||||
} from './filter'
 | 
					} from './filter'
 | 
				
			||||||
 | 
					import { ToyShopEntity } from './__tests__/toy-shop.entity'
 | 
				
			||||||
 | 
					import { ToyShopAddressEntity } from './__tests__/toy-shop-address.entity'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isoStringToDate = (isoString) => new Date(isoString)
 | 
					const isoStringToDate = (isoString) => new Date(isoString)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -23,10 +25,15 @@ describe('paginate', () => {
 | 
				
			|||||||
    let dataSource: DataSource
 | 
					    let dataSource: DataSource
 | 
				
			||||||
    let catRepo: Repository<CatEntity>
 | 
					    let catRepo: Repository<CatEntity>
 | 
				
			||||||
    let catToyRepo: Repository<CatToyEntity>
 | 
					    let catToyRepo: Repository<CatToyEntity>
 | 
				
			||||||
 | 
					    let toyShopRepo: Repository<ToyShopEntity>
 | 
				
			||||||
 | 
					    let toyShopAddressRepository: Repository<ToyShopAddressEntity>
 | 
				
			||||||
    let catHomeRepo: Repository<CatHomeEntity>
 | 
					    let catHomeRepo: Repository<CatHomeEntity>
 | 
				
			||||||
    let catHomePillowRepo: Repository<CatHomePillowEntity>
 | 
					    let catHomePillowRepo: Repository<CatHomePillowEntity>
 | 
				
			||||||
    let cats: CatEntity[]
 | 
					    let cats: CatEntity[]
 | 
				
			||||||
    let catToys: CatToyEntity[]
 | 
					    let catToys: CatToyEntity[]
 | 
				
			||||||
 | 
					    let catToysWithoutShop: CatToyEntity[]
 | 
				
			||||||
 | 
					    let toyShopsAddresses: ToyShopAddressEntity[]
 | 
				
			||||||
 | 
					    let toysShops: ToyShopEntity[]
 | 
				
			||||||
    let catHomes: CatHomeEntity[]
 | 
					    let catHomes: CatHomeEntity[]
 | 
				
			||||||
    let catHomePillows: CatHomePillowEntity[]
 | 
					    let catHomePillows: CatHomePillowEntity[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -47,13 +54,22 @@ describe('paginate', () => {
 | 
				
			|||||||
                  }),
 | 
					                  }),
 | 
				
			||||||
            synchronize: true,
 | 
					            synchronize: true,
 | 
				
			||||||
            logging: ['error'],
 | 
					            logging: ['error'],
 | 
				
			||||||
            entities: [CatEntity, CatToyEntity, CatHomeEntity, CatHomePillowEntity],
 | 
					            entities: [
 | 
				
			||||||
 | 
					                CatEntity,
 | 
				
			||||||
 | 
					                CatToyEntity,
 | 
				
			||||||
 | 
					                ToyShopAddressEntity,
 | 
				
			||||||
 | 
					                CatHomeEntity,
 | 
				
			||||||
 | 
					                CatHomePillowEntity,
 | 
				
			||||||
 | 
					                ToyShopEntity,
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        await dataSource.initialize()
 | 
					        await dataSource.initialize()
 | 
				
			||||||
        catRepo = dataSource.getRepository(CatEntity)
 | 
					        catRepo = dataSource.getRepository(CatEntity)
 | 
				
			||||||
        catToyRepo = dataSource.getRepository(CatToyEntity)
 | 
					        catToyRepo = dataSource.getRepository(CatToyEntity)
 | 
				
			||||||
        catHomeRepo = dataSource.getRepository(CatHomeEntity)
 | 
					        catHomeRepo = dataSource.getRepository(CatHomeEntity)
 | 
				
			||||||
        catHomePillowRepo = dataSource.getRepository(CatHomePillowEntity)
 | 
					        catHomePillowRepo = dataSource.getRepository(CatHomePillowEntity)
 | 
				
			||||||
 | 
					        toyShopRepo = dataSource.getRepository(ToyShopEntity)
 | 
				
			||||||
 | 
					        toyShopAddressRepository = dataSource.getRepository(ToyShopAddressEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cats = await catRepo.save([
 | 
					        cats = await catRepo.save([
 | 
				
			||||||
            catRepo.create({
 | 
					            catRepo.create({
 | 
				
			||||||
@ -97,12 +113,41 @@ describe('paginate', () => {
 | 
				
			|||||||
                size: { height: 10, width: 5, length: 15 },
 | 
					                size: { height: 10, width: 5, length: 15 },
 | 
				
			||||||
            }),
 | 
					            }),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        toyShopsAddresses = await toyShopAddressRepository.save([
 | 
				
			||||||
 | 
					            toyShopAddressRepository.create({ address: '123 Main St' }),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        toysShops = await toyShopRepo.save([
 | 
				
			||||||
 | 
					            toyShopRepo.create({ shopName: 'Best Toys', address: toyShopsAddresses[0] }),
 | 
				
			||||||
 | 
					            toyShopRepo.create({ shopName: 'Lovely Toys' }),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        catToys = await catToyRepo.save([
 | 
					        catToys = await catToyRepo.save([
 | 
				
			||||||
            catToyRepo.create({ name: 'Fuzzy Thing', cat: cats[0], size: { height: 10, width: 10, length: 10 } }),
 | 
					            catToyRepo.create({ name: 'Fuzzy Thing', cat: cats[0], size: { height: 10, width: 10, length: 10 } }),
 | 
				
			||||||
            catToyRepo.create({ name: 'Stuffed Mouse', cat: cats[0], size: { height: 5, width: 5, length: 12 } }),
 | 
					            catToyRepo.create({
 | 
				
			||||||
            catToyRepo.create({ name: 'Mouse', cat: cats[0], size: { height: 6, width: 4, length: 13 } }),
 | 
					                name: 'Stuffed Mouse',
 | 
				
			||||||
 | 
					                shop: toysShops[0],
 | 
				
			||||||
 | 
					                cat: cats[0],
 | 
				
			||||||
 | 
					                size: { height: 5, width: 5, length: 12 },
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            catToyRepo.create({
 | 
				
			||||||
 | 
					                name: 'Mouse',
 | 
				
			||||||
 | 
					                shop: toysShops[1],
 | 
				
			||||||
 | 
					                cat: cats[0],
 | 
				
			||||||
 | 
					                size: { height: 6, width: 4, length: 13 },
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
            catToyRepo.create({ name: 'String', cat: cats[1], size: { height: 1, width: 1, length: 50 } }),
 | 
					            catToyRepo.create({ name: 'String', cat: cats[1], size: { height: 1, width: 1, length: 50 } }),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        catToysWithoutShop = catToys.map(({ shop: _, ...other }) => {
 | 
				
			||||||
 | 
					            const newInstance = new CatToyEntity()
 | 
				
			||||||
 | 
					            for (const otherKey in other) {
 | 
				
			||||||
 | 
					                newInstance[otherKey] = other[otherKey]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return newInstance
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        catHomes = await catHomeRepo.save([
 | 
					        catHomes = await catHomeRepo.save([
 | 
				
			||||||
            catHomeRepo.create({ name: 'Box', cat: cats[0] }),
 | 
					            catHomeRepo.create({ name: 'Box', cat: cats[0] }),
 | 
				
			||||||
            catHomeRepo.create({ name: 'House', cat: cats[1] }),
 | 
					            catHomeRepo.create({ name: 'House', cat: cats[1] }),
 | 
				
			||||||
@ -594,7 +639,7 @@ describe('paginate', () => {
 | 
				
			|||||||
        const result = await paginate<CatToyEntity>(query, catToyRepo, config)
 | 
					        const result = await paginate<CatToyEntity>(query, catToyRepo, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(result.meta.search).toStrictEqual('Milo')
 | 
					        expect(result.meta.search).toStrictEqual('Milo')
 | 
				
			||||||
        expect(result.data).toStrictEqual([catToys[0], catToys[1], catToys[2]])
 | 
					        expect(result.data).toStrictEqual([catToysWithoutShop[0], catToysWithoutShop[1], catToysWithoutShop[2]])
 | 
				
			||||||
        expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Milo')
 | 
					        expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Milo')
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -616,9 +661,9 @@ describe('paginate', () => {
 | 
				
			|||||||
        const result = await paginate<CatEntity>(query, catRepo, config)
 | 
					        const result = await paginate<CatEntity>(query, catRepo, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(result.meta.search).toStrictEqual('Mouse')
 | 
					        expect(result.meta.search).toStrictEqual('Mouse')
 | 
				
			||||||
        const toy = clone(catToys[1])
 | 
					        const toy = clone(catToysWithoutShop[1])
 | 
				
			||||||
        delete toy.cat
 | 
					        delete toy.cat
 | 
				
			||||||
        const toy2 = clone(catToys[2])
 | 
					        const toy2 = clone(catToysWithoutShop[2])
 | 
				
			||||||
        delete toy2.cat
 | 
					        delete toy2.cat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(result.data).toStrictEqual([Object.assign(clone(cats[0]), { toys: [toy2, toy] })])
 | 
					        expect(result.data).toStrictEqual([Object.assign(clone(cats[0]), { toys: [toy2, toy] })])
 | 
				
			||||||
@ -661,13 +706,15 @@ describe('paginate', () => {
 | 
				
			|||||||
        const result = await paginate<CatToyEntity>(query, catToyRepo, config)
 | 
					        const result = await paginate<CatToyEntity>(query, catToyRepo, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(result.meta.search).toStrictEqual('Milo')
 | 
					        expect(result.meta.search).toStrictEqual('Milo')
 | 
				
			||||||
        expect(result.data).toStrictEqual([catToys[0], catToys[1], catToys[2]].sort((a, b) => b.cat.id - a.cat.id))
 | 
					        expect(result.data).toStrictEqual(
 | 
				
			||||||
 | 
					            [catToysWithoutShop[0], catToysWithoutShop[1], catToysWithoutShop[2]].sort((a, b) => b.cat.id - a.cat.id)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        expect(result.links.current).toBe('?page=1&limit=20&sortBy=cat.id:DESC&search=Milo')
 | 
					        expect(result.links.current).toBe('?page=1&limit=20&sortBy=cat.id:DESC&search=Milo')
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('should return result based on sort on one-to-many relation', async () => {
 | 
					    it('should return result based on sort on one-to-many relation', async () => {
 | 
				
			||||||
        const config: PaginateConfig<CatEntity> = {
 | 
					        const config: PaginateConfig<CatEntity> = {
 | 
				
			||||||
            relations: ['toys'],
 | 
					            relations: ['toys', 'toys.shop', 'toys.shop.address'],
 | 
				
			||||||
            sortableColumns: ['id', 'name', 'toys.id'],
 | 
					            sortableColumns: ['id', 'name', 'toys.id'],
 | 
				
			||||||
            searchableColumns: ['name', 'toys.name'],
 | 
					            searchableColumns: ['name', 'toys.name'],
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -684,6 +731,9 @@ describe('paginate', () => {
 | 
				
			|||||||
        delete toy1.cat
 | 
					        delete toy1.cat
 | 
				
			||||||
        const toy2 = clone(catToys[2])
 | 
					        const toy2 = clone(catToys[2])
 | 
				
			||||||
        delete toy2.cat
 | 
					        delete toy2.cat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        delete result.data[0].toys[0].shop.address
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(result.data).toStrictEqual([Object.assign(clone(cats[0]), { toys: [toy2, toy1] })])
 | 
					        expect(result.data).toStrictEqual([Object.assign(clone(cats[0]), { toys: [toy2, toy1] })])
 | 
				
			||||||
        expect(result.links.current).toBe('?page=1&limit=20&sortBy=toys.id:DESC&search=Mouse')
 | 
					        expect(result.links.current).toBe('?page=1&limit=20&sortBy=toys.id:DESC&search=Mouse')
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
@ -865,6 +915,103 @@ describe('paginate', () => {
 | 
				
			|||||||
        expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC')
 | 
					        expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC')
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should return valid data filtering by not id field many-to-one', async () => {
 | 
				
			||||||
 | 
					        const config: PaginateConfig<CatToyEntity> = {
 | 
				
			||||||
 | 
					            sortableColumns: ['id', 'name'],
 | 
				
			||||||
 | 
					            relations: ['cat'],
 | 
				
			||||||
 | 
					            where: {
 | 
				
			||||||
 | 
					                cat: {
 | 
				
			||||||
 | 
					                    name: cats[0].name,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const query: PaginateQuery = {
 | 
				
			||||||
 | 
					            path: '',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const result = await paginate<CatToyEntity>(query, catToyRepo, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(result.meta.totalItems).toBe(3)
 | 
				
			||||||
 | 
					        result.data.forEach((toy) => {
 | 
				
			||||||
 | 
					            expect(toy.cat.id).toBe(cats[0].id)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should return result based on where one-to-many relation', async () => {
 | 
				
			||||||
 | 
					        const config: PaginateConfig<CatEntity> = {
 | 
				
			||||||
 | 
					            relations: ['toys'],
 | 
				
			||||||
 | 
					            sortableColumns: ['id', 'name'],
 | 
				
			||||||
 | 
					            where: {
 | 
				
			||||||
 | 
					                toys: {
 | 
				
			||||||
 | 
					                    name: 'Stuffed Mouse',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const query: PaginateQuery = {
 | 
				
			||||||
 | 
					            path: '',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const result = await paginate<CatEntity>(query, catRepo, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(result.data.length).toBe(1)
 | 
				
			||||||
 | 
					        expect(result.data[0].toys.length).toBe(1)
 | 
				
			||||||
 | 
					        expect(result.data[0].toys[0].name).toBe('Stuffed Mouse')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should return all cats with a toys from the lovely shop', async () => {
 | 
				
			||||||
 | 
					        const config: PaginateConfig<CatEntity> = {
 | 
				
			||||||
 | 
					            relations: ['toys', 'toys.shop'],
 | 
				
			||||||
 | 
					            sortableColumns: ['id', 'name'],
 | 
				
			||||||
 | 
					            where: {
 | 
				
			||||||
 | 
					                toys: {
 | 
				
			||||||
 | 
					                    shop: {
 | 
				
			||||||
 | 
					                        shopName: 'Lovely Toys',
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const query: PaginateQuery = {
 | 
				
			||||||
 | 
					            path: '',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const result = await paginate<CatEntity>(query, catRepo, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(result.data.length).toBe(1)
 | 
				
			||||||
 | 
					        expect(result.data[0].toys.length).toBe(1)
 | 
				
			||||||
 | 
					        expect(result.data[0].toys[0].shop.id).toStrictEqual(toysShops[1].id)
 | 
				
			||||||
 | 
					        expect(result.data[0].toys[0].name).toBe('Mouse')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should return all cats from shop where street name like 123', async () => {
 | 
				
			||||||
 | 
					        const config: PaginateConfig<CatEntity> = {
 | 
				
			||||||
 | 
					            relations: ['toys', 'toys.shop', 'toys.shop.address'],
 | 
				
			||||||
 | 
					            sortableColumns: ['id', 'name'],
 | 
				
			||||||
 | 
					            where: {
 | 
				
			||||||
 | 
					                toys: {
 | 
				
			||||||
 | 
					                    shop: {
 | 
				
			||||||
 | 
					                        address: {
 | 
				
			||||||
 | 
					                            address: Like('%123%'),
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const query: PaginateQuery = {
 | 
				
			||||||
 | 
					            path: '',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const result = await paginate<CatEntity>(query, catRepo, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(result.data.length).toBe(1)
 | 
				
			||||||
 | 
					        expect(result.data[0].toys.length).toBe(1)
 | 
				
			||||||
 | 
					        expect(result.data[0].toys[0].shop).toStrictEqual(toysShops[0])
 | 
				
			||||||
 | 
					        expect(result.data[0].toys[0].name).toBe('Stuffed Mouse')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('should return result based on filter on many-to-one relation', async () => {
 | 
					    it('should return result based on filter on many-to-one relation', async () => {
 | 
				
			||||||
        const config: PaginateConfig<CatToyEntity> = {
 | 
					        const config: PaginateConfig<CatToyEntity> = {
 | 
				
			||||||
            relations: ['cat'],
 | 
					            relations: ['cat'],
 | 
				
			||||||
@ -908,9 +1055,9 @@ describe('paginate', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const cat1 = clone(cats[0])
 | 
					        const cat1 = clone(cats[0])
 | 
				
			||||||
        const cat2 = clone(cats[1])
 | 
					        const cat2 = clone(cats[1])
 | 
				
			||||||
        const catToys1 = clone(catToys[0])
 | 
					        const catToys1 = clone(catToysWithoutShop[0])
 | 
				
			||||||
        const catToys2 = clone(catToys[2])
 | 
					        const catToys2 = clone(catToysWithoutShop[2])
 | 
				
			||||||
        const catToys3 = clone(catToys[3])
 | 
					        const catToys3 = clone(catToysWithoutShop[3])
 | 
				
			||||||
        delete catToys1.cat
 | 
					        delete catToys1.cat
 | 
				
			||||||
        delete catToys2.cat
 | 
					        delete catToys2.cat
 | 
				
			||||||
        delete catToys3.cat
 | 
					        delete catToys3.cat
 | 
				
			||||||
@ -1062,7 +1209,7 @@ describe('paginate', () => {
 | 
				
			|||||||
        copyCats[0].home = copyHomes[0]
 | 
					        copyCats[0].home = copyHomes[0]
 | 
				
			||||||
        copyCats[1].home = copyHomes[1]
 | 
					        copyCats[1].home = copyHomes[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const copyToys = catToys.map((toy: CatToyEntity) => {
 | 
					        const copyToys = catToysWithoutShop.map((toy: CatToyEntity) => {
 | 
				
			||||||
            const copy = clone(toy)
 | 
					            const copy = clone(toy)
 | 
				
			||||||
            delete copy.cat
 | 
					            delete copy.cat
 | 
				
			||||||
            return copy
 | 
					            return copy
 | 
				
			||||||
@ -1095,16 +1242,16 @@ describe('paginate', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const result = await paginate<CatEntity>(query, catRepo, config)
 | 
					        const result = await paginate<CatEntity>(query, catRepo, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const toy0 = clone(catToys[0])
 | 
					        const toy0 = clone(catToysWithoutShop[0])
 | 
				
			||||||
        delete toy0.cat
 | 
					        delete toy0.cat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const toy1 = clone(catToys[1])
 | 
					        const toy1 = clone(catToysWithoutShop[1])
 | 
				
			||||||
        delete toy1.cat
 | 
					        delete toy1.cat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const toy2 = clone(catToys[2])
 | 
					        const toy2 = clone(catToysWithoutShop[2])
 | 
				
			||||||
        delete toy2.cat
 | 
					        delete toy2.cat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const toy3 = clone(catToys[3])
 | 
					        const toy3 = clone(catToysWithoutShop[3])
 | 
				
			||||||
        delete toy3.cat
 | 
					        delete toy3.cat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const orderedCats = [
 | 
					        const orderedCats = [
 | 
				
			||||||
@ -1136,7 +1283,7 @@ describe('paginate', () => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const result = await paginate<CatToyEntity>(query, catToyRepo, config)
 | 
					        const result = await paginate<CatToyEntity>(query, catToyRepo, config)
 | 
				
			||||||
        const orderedToys = [catToys[3], catToys[0], catToys[2], catToys[1]]
 | 
					        const orderedToys = [catToysWithoutShop[3], catToysWithoutShop[0], catToysWithoutShop[2], catToysWithoutShop[1]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(result.data).toStrictEqual(orderedToys)
 | 
					        expect(result.data).toStrictEqual(orderedToys)
 | 
				
			||||||
        expect(result.links.current).toBe(
 | 
					        expect(result.links.current).toBe(
 | 
				
			||||||
@ -1279,7 +1426,12 @@ describe('paginate', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const result = await paginate<CatToyEntity>(query, catToyRepo, config)
 | 
					        const result = await paginate<CatToyEntity>(query, catToyRepo, config)
 | 
				
			||||||
        expect(result.meta.search).toStrictEqual('1')
 | 
					        expect(result.meta.search).toStrictEqual('1')
 | 
				
			||||||
        expect(result.data).toStrictEqual([catToys[3], catToys[0], catToys[1], catToys[2]])
 | 
					        expect(result.data).toStrictEqual([
 | 
				
			||||||
 | 
					            catToysWithoutShop[3],
 | 
				
			||||||
 | 
					            catToysWithoutShop[0],
 | 
				
			||||||
 | 
					            catToysWithoutShop[1],
 | 
				
			||||||
 | 
					            catToysWithoutShop[2],
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
        expect(result.links.current).toBe('?page=1&limit=20&sortBy=cat.(size.height):DESC&search=1')
 | 
					        expect(result.links.current).toBe('?page=1&limit=20&sortBy=cat.(size.height):DESC&search=1')
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,32 +1,33 @@
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    Brackets,
 | 
				
			||||||
 | 
					    FindOperator,
 | 
				
			||||||
 | 
					    FindOptionsRelationByString,
 | 
				
			||||||
 | 
					    FindOptionsRelations,
 | 
				
			||||||
 | 
					    FindOptionsUtils,
 | 
				
			||||||
 | 
					    FindOptionsWhere,
 | 
				
			||||||
 | 
					    ObjectLiteral,
 | 
				
			||||||
    Repository,
 | 
					    Repository,
 | 
				
			||||||
    SelectQueryBuilder,
 | 
					    SelectQueryBuilder,
 | 
				
			||||||
    Brackets,
 | 
					 | 
				
			||||||
    FindOptionsWhere,
 | 
					 | 
				
			||||||
    FindOptionsRelations,
 | 
					 | 
				
			||||||
    ObjectLiteral,
 | 
					 | 
				
			||||||
    FindOptionsUtils,
 | 
					 | 
				
			||||||
    FindOptionsRelationByString,
 | 
					 | 
				
			||||||
} from 'typeorm'
 | 
					} from 'typeorm'
 | 
				
			||||||
import { PaginateQuery } from './decorator'
 | 
					import { PaginateQuery } from './decorator'
 | 
				
			||||||
import { ServiceUnavailableException, Logger } from '@nestjs/common'
 | 
					import { Logger, ServiceUnavailableException } from '@nestjs/common'
 | 
				
			||||||
import { mapKeys } from 'lodash'
 | 
					import { mapKeys } from 'lodash'
 | 
				
			||||||
import { stringify } from 'querystring'
 | 
					import { stringify } from 'querystring'
 | 
				
			||||||
import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause'
 | 
					import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    checkIsRelation,
 | 
					 | 
				
			||||||
    checkIsEmbedded,
 | 
					    checkIsEmbedded,
 | 
				
			||||||
 | 
					    checkIsRelation,
 | 
				
			||||||
    Column,
 | 
					    Column,
 | 
				
			||||||
    extractVirtualProperty,
 | 
					    extractVirtualProperty,
 | 
				
			||||||
    fixColumnAlias,
 | 
					    fixColumnAlias,
 | 
				
			||||||
    getPropertiesByColumnName,
 | 
					    getPropertiesByColumnName,
 | 
				
			||||||
 | 
					    getQueryUrlComponents,
 | 
				
			||||||
 | 
					    includesAllPrimaryKeyColumns,
 | 
				
			||||||
 | 
					    isEntityKey,
 | 
				
			||||||
    Order,
 | 
					    Order,
 | 
				
			||||||
    positiveNumberOrDefault,
 | 
					    positiveNumberOrDefault,
 | 
				
			||||||
    RelationColumn,
 | 
					    RelationColumn,
 | 
				
			||||||
    SortBy,
 | 
					    SortBy,
 | 
				
			||||||
    includesAllPrimaryKeyColumns,
 | 
					 | 
				
			||||||
    isEntityKey,
 | 
					 | 
				
			||||||
    getQueryUrlComponents,
 | 
					 | 
				
			||||||
} from './helper'
 | 
					} from './helper'
 | 
				
			||||||
import { addFilter, FilterOperator, FilterSuffix } from './filter'
 | 
					import { addFilter, FilterOperator, FilterSuffix } from './filter'
 | 
				
			||||||
import { OrmUtils } from 'typeorm/util/OrmUtils'
 | 
					import { OrmUtils } from 'typeorm/util/OrmUtils'
 | 
				
			||||||
@ -46,7 +47,9 @@ export class Paginated<T> {
 | 
				
			|||||||
        searchBy: Column<T>[]
 | 
					        searchBy: Column<T>[]
 | 
				
			||||||
        search: string
 | 
					        search: string
 | 
				
			||||||
        select: string[]
 | 
					        select: string[]
 | 
				
			||||||
        filter?: { [column: string]: string | string[] }
 | 
					        filter?: {
 | 
				
			||||||
 | 
					            [column: string]: string | string[]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    links: {
 | 
					    links: {
 | 
				
			||||||
        first?: string
 | 
					        first?: string
 | 
				
			||||||
@ -86,6 +89,47 @@ export const DEFAULT_MAX_LIMIT = 100
 | 
				
			|||||||
export const DEFAULT_LIMIT = 20
 | 
					export const DEFAULT_LIMIT = 20
 | 
				
			||||||
export const NO_PAGINATION = 0
 | 
					export const NO_PAGINATION = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function generateWhereStatement<T>(
 | 
				
			||||||
 | 
					    queryBuilder: SelectQueryBuilder<T>,
 | 
				
			||||||
 | 
					    obj: FindOptionsWhere<T> | FindOptionsWhere<T>[]
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    const toTransform = Array.isArray(obj) ? obj : [obj]
 | 
				
			||||||
 | 
					    return toTransform.map((item) => flattenWhereAndTransform(queryBuilder, item).join(' AND ')).join(' OR ')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function flattenWhereAndTransform<T>(
 | 
				
			||||||
 | 
					    queryBuilder: SelectQueryBuilder<T>,
 | 
				
			||||||
 | 
					    obj: FindOptionsWhere<T>,
 | 
				
			||||||
 | 
					    separator = '.',
 | 
				
			||||||
 | 
					    parentKey = ''
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    return Object.entries(obj).flatMap(([key, value]) => {
 | 
				
			||||||
 | 
					        if (obj.hasOwnProperty(key)) {
 | 
				
			||||||
 | 
					            const joinedKey = parentKey ? `${parentKey}${separator}${key}` : key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (typeof value === 'object' && value !== null && !(value instanceof FindOperator)) {
 | 
				
			||||||
 | 
					                return flattenWhereAndTransform(queryBuilder, value as FindOptionsWhere<T>, separator, joinedKey)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                const property = getPropertiesByColumnName(joinedKey)
 | 
				
			||||||
 | 
					                const { isVirtualProperty, query: virtualQuery } = extractVirtualProperty(queryBuilder, property)
 | 
				
			||||||
 | 
					                const isRelation = checkIsRelation(queryBuilder, property.propertyPath)
 | 
				
			||||||
 | 
					                const isEmbedded = checkIsEmbedded(queryBuilder, property.propertyPath)
 | 
				
			||||||
 | 
					                const alias = fixColumnAlias(
 | 
				
			||||||
 | 
					                    property,
 | 
				
			||||||
 | 
					                    queryBuilder.alias,
 | 
				
			||||||
 | 
					                    isRelation,
 | 
				
			||||||
 | 
					                    isVirtualProperty,
 | 
				
			||||||
 | 
					                    isEmbedded,
 | 
				
			||||||
 | 
					                    virtualQuery
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                return queryBuilder['createWhereConditionExpression'](
 | 
				
			||||||
 | 
					                    queryBuilder['getWherePredicateCondition'](alias, value)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function paginate<T extends ObjectLiteral>(
 | 
					export async function paginate<T extends ObjectLiteral>(
 | 
				
			||||||
    query: PaginateQuery,
 | 
					    query: PaginateQuery,
 | 
				
			||||||
    repo: Repository<T> | SelectQueryBuilder<T>,
 | 
					    repo: Repository<T> | SelectQueryBuilder<T>,
 | 
				
			||||||
@ -199,8 +243,9 @@ export async function paginate<T extends ObjectLiteral>(
 | 
				
			|||||||
        queryBuilder.select(cols)
 | 
					        queryBuilder.select(cols)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.where) {
 | 
					    if (config.where && repo instanceof Repository) {
 | 
				
			||||||
        queryBuilder.andWhere(new Brackets((qb) => qb.andWhere(config.where)))
 | 
					        const baseWhereStr = generateWhereStatement(queryBuilder, config.where)
 | 
				
			||||||
 | 
					        queryBuilder.andWhere(`(${baseWhereStr})`)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.withDeleted) {
 | 
					    if (config.withDeleted) {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user