import { AggregateQuery , Filter , getFilterFields , Paging , Query , SortField } from '@nestjs-query/core' ; import sequelize , { FindOptions , Filterable , DestroyOptions , Order , OrderItem , UpdateOptions , CountOptions , Association , Projectable , GroupOption , } from 'sequelize' ; import { Model , ModelCtor } from 'sequelize-typescript' ; import { AggregateBuilder } from './aggregate.builder' ; import { WhereBuilder } from './where.builder' ; internal interface Sortable { order ? : Order ; } internal interface Pageable { limit ? : number ; offset ? : number ; } interface Groupable { group ? : GroupOption ; } internal export class FilterQueryBuilder < Entity extends Model < Entity , Partial < Entity > > > { constructor ( readonly model , readonly whereBuilder = new WhereBuilder < Entity > ( ) , readonly aggregateBuilder = new AggregateBuilder < Entity > ( model ) ) { } param query findOptions ( query ) { let opts = this . applyAssociationIncludes ( { subQuery : false } , query . filter ) ; opts = this . applyFilter ( opts , query . filter ) ; opts = this . applySorting ( opts , query . sorting ) ; opts = this . applyPaging ( opts , query . paging ) ; return opts ; } param pk param query findByIdOptions ( pk , query ) { let opts = this . applyAssociationIncludes ( { subQuery : false } , query . filter ) ; opts = this . applyFilter ( opts , { ... ( query . filter ?? ( { } as Filter < Entity > ) ) , [ this . model . primaryKeyAttribute ] : { [ Array . isArray ( pk ) ? 'in' : 'eq' ] : pk } , } ) ; opts = this . applySorting ( opts , query . sorting ) ; opts = this . applyPaging ( opts , query . paging ) ; return opts ; } param query param aggregate aggregateOptions ( query , aggregate ) { let opts = { raw : true } ; opts = this . applyAggregate ( opts , aggregate ) ; opts = this . applyFilter ( opts , query . filter ) ; opts = this . applyAggregateSorting ( opts , aggregate . groupBy ) ; opts = this . applyGroupBy ( opts , aggregate . groupBy ) ; return opts ; } relationAggregateOptions ( query , aggregate ) { let opts = { joinTableAttributes : [ ] , raw : true } as FindOptions ; opts = this . applyAggregate ( opts , aggregate ) ; opts = this . applyFilter ( opts , query . filter ) ; opts = this . applyAggregateSorting ( opts , aggregate . groupBy ) ; opts = this . applyGroupBy ( opts , aggregate . groupBy ) ; return opts ; } countOptions ( query ) { let opts = this . applyAssociationIncludes ( { } , query . filter ) ; opts . distinct = true ; opts = this . applyFilter ( opts , query . filter ) ; return opts ; } param query destroyOptions ( query ) { let opts = { } ; opts = this . applyFilter ( opts , query . filter ) ; opts = this . applyPaging ( opts , query . paging ) ; return opts ; } param query updateOptions ( query ) { let opts = { where : { } } ; opts = this . applyFilter ( opts , query . filter ) ; opts = this . applyPaging ( opts , query . paging ) ; return opts ; } param qb param paging applyPaging < P extends Pageable > ( qb , paging ? ) { if ( ! paging ) { return qb ; } if ( paging . limit !== undefined ) { qb . limit = paging . limit ; } if ( paging . offset !== undefined ) { qb . offset = paging . offset ; } return qb ; } param filterable param filter applyFilter < Where extends Filterable > ( filterable , filter ? ) { if ( ! filter ) { return filterable ; } filterable . where = this . whereBuilder . build ( filter , this . getReferencedRelations ( filter ) ) ; return filterable ; } param qb param sorts applySorting < T extends Sortable > ( qb , sorts ? ) { if ( ! sorts ) { return qb ; } qb . order = sorts . map ( ( { field , direction , nulls } ) : OrderItem => { const col = `${ field as string }` ; const dir = [ direction ] ; if ( nulls ) { dir . push ( nulls ) ; } return [ col , dir . join ( ' ' ) ] ; } ) ; return qb ; } private applyAggregate < P extends Projectable > ( opts , aggregate ) { opts . attributes = this . aggregateBuilder . build ( aggregate ) . attributes ; return opts ; } applyGroupBy < T extends Groupable > ( qb , groupBy ? ) { if ( ! groupBy ) { return qb ; } qb . group = groupBy . map ( ( field ) => { const colName = this . model . rawAttributes [ field as string ] . field ; return sequelize . col ( colName ?? ( field as string ) ) ; } ) ; return qb ; } applyAggregateSorting < T extends Sortable > ( qb , groupBy ? ) { if ( ! groupBy ) { return qb ; } qb . order = groupBy . map ( ( field ) : OrderItem => { const colName = this . model . rawAttributes [ field as string ] . field ; const col = sequelize . col ( colName ?? ( field as string ) ) ; return [ col , 'ASC' ] ; } ) ; return qb ; } private applyAssociationIncludes < Opts extends FindOptions | CountOptions > ( findOpts , filter ? ) { if ( ! filter ) { return findOpts ; } const referencedRelations = this . getReferencedRelations ( filter ) ; return [ ... referencedRelations . values ( ) ] . reduce ( ( find , association ) => { const { include = [ ] } = find ; find . include = [ ... ( Array . isArray ( include ) ? include : [ include ] ) , { association , attributes : [ ] } ] ; return find ; } , findOpts ) ; } private getReferencedRelations ( filter ) { const { relationNames } = this ; const referencedFields = getFilterFields ( filter ) ; const referencedRelations = referencedFields . filter ( ( f ) => relationNames . includes ( f ) ) ; return referencedRelations . reduce ( ( map , r ) => map . set ( r , this . model . associations [ r ] ) , new Map < string , Association > ( ) ) ; } private get relationNames ( ) : string [ ] { return Object . keys ( this . model . associations || { } ) ; } }