import { Query , Class , AssemblerFactory , Filter , AggregateQuery , AggregateResponse , ModifyRelationOptions , GetByIdOptions , FindRelationOptions , } from '@nestjs-query/core' ; import { Model , ModelCtor } from 'sequelize-typescript' ; import { ModelCtor as SequelizeModelCtor } from 'sequelize' ; import { AggregateBuilder , FilterQueryBuilder } from '../query' ; interface SequelizeAssociation { target : SequelizeModelCtor < any > ; isMultiAssociation : boolean ; isSingleAssociation : boolean ; } internal export abstract class RelationQueryService < Entity extends Model < Entity , Partial < Entity > > > { abstract filterQueryBuilder : FilterQueryBuilder < Entity > ; abstract model : ModelCtor < Entity > ; abstract getById ( id , opts ? ) ; param RelationClass param relationName param entities param query async queryRelations < Relation > ( RelationClass , relationName , entities , query ) ; param RelationClass param dto param relationName param query async queryRelations < Relation > ( RelationClass , relationName , dto , query ) ; async queryRelations < Relation > ( RelationClass , relationName , dto , query ) { if ( Array . isArray ( dto ) ) { return this . batchQueryRelations ( RelationClass , relationName , dto , query ) ; } const relationEntity = this . getRelationEntity ( relationName ) ; const assembler = AssemblerFactory . getAssembler ( RelationClass , relationEntity ) ; const relationQueryBuilder = this . getRelationQueryBuilder < Model > ( relationEntity ) ; const relations = await this . ensureIsEntity ( dto ) . $get ( relationName as keyof Entity , relationQueryBuilder . findOptions ( assembler . convertQuery ( query ) ) ) ; return assembler . convertToDTOs ( relations as unknown as Model [ ] ) ; } async aggregateRelations < Relation > ( RelationClass , relationName , entities , filter , aggregate ) ; param RelationClass param dto param relationName param filter param aggregate async aggregateRelations < Relation > ( RelationClass , relationName , dto , filter , aggregate ) ; async aggregateRelations < Relation > ( RelationClass , relationName , dto , filter , aggregate ) { if ( Array . isArray ( dto ) ) { return this . batchAggregateRelations ( RelationClass , relationName , dto , filter , aggregate ) ; } const relationEntity = this . getRelationEntity ( relationName ) ; const assembler = AssemblerFactory . getAssembler ( RelationClass , relationEntity ) ; const relationQueryBuilder = this . getRelationQueryBuilder < Model > ( relationEntity ) ; const results = ( await this . ensureIsEntity ( dto ) . $get ( relationName as keyof Entity , relationQueryBuilder . relationAggregateOptions ( assembler . convertQuery ( { filter } ) , assembler . convertAggregateQuery ( aggregate ) ) ) ) as unknown as Record < string , unknown > [ ] ; return AggregateBuilder . convertToAggregateResponse ( results ) . map ( ( a ) => assembler . convertAggregateResponse ( a ) ) ; } countRelations < Relation > ( RelationClass , relationName , entities , filter ) ; countRelations < Relation > ( RelationClass , relationName , dto , filter ) ; async countRelations < Relation > ( RelationClass , relationName , dto , filter ) { if ( Array . isArray ( dto ) ) { return this . batchCountRelations ( RelationClass , relationName , dto , filter ) ; } const relationEntity = this . getRelationEntity ( relationName ) ; const assembler = AssemblerFactory . getAssembler ( RelationClass , relationEntity ) ; const relationQueryBuilder = this . getRelationQueryBuilder < Model > ( relationEntity ) ; return this . ensureIsEntity ( dto ) . $count ( relationName , relationQueryBuilder . countOptions ( assembler . convertQuery ( { filter } ) ) ) ; } param RelationClass param relationName param dtos param opts async findRelation < Relation > ( RelationClass , relationName , dtos , opts ? ) ; param RelationClass param dto param relationName param opts async findRelation < Relation > ( RelationClass , relationName , dto , opts ? ) ; async findRelation < Relation > ( RelationClass , relationName , dto , opts ? ) { if ( Array . isArray ( dto ) ) { return this . batchFindRelations ( RelationClass , relationName , dto , opts ) ; } const relationEntity = this . getRelationEntity ( relationName ) ; const assembler = AssemblerFactory . getAssembler ( RelationClass , relationEntity ) ; const relationQueryBuilder = this . getRelationQueryBuilder ( relationEntity ) ; const relation = await this . ensureIsEntity ( dto ) . $get ( relationName as keyof Entity , relationQueryBuilder . findOptions ( opts ?? { } ) ) ; if ( ! relation ) { return undefined ; } return assembler . convertToDTO ( relation as unknown as Model ) ; } param id param relationName param relationIds param opts async addRelations < Relation > ( relationName , id , relationIds , opts ? ) { const entity = await this . getById ( id , opts ) ; const relations = await this . getRelations ( relationName , relationIds , opts ?. relationFilter ) ; if ( ! this . foundAllRelations ( relationIds , relations ) ) { throw new Error ( `Unable to find all ${ relationName } to add to ${ this . model . name }` ) ; } await entity . $add ( relationName , relationIds ) ; return entity ; } param id param relationName param relationIds param opts async setRelations < Relation > ( relationName , id , relationIds , opts ? ) { const entity = await this . getById ( id , opts ) ; if ( relationIds . length ) { const relations = await this . getRelations ( relationName , relationIds , opts ?. relationFilter ) ; if ( relations . length !== relationIds . length ) { throw new Error ( `Unable to find all ${ relationName } to set on ${ this . model . name }` ) ; } } await entity . $set ( relationName as keyof Entity , relationIds ) ; return entity ; } param id param relationName param relationId param opts async setRelation < Relation > ( relationName , id , relationId , opts ? ) { const entity = await this . getById ( id , opts ) ; const relation = ( await this . getRelations ( relationName , [ relationId ] , opts ?. relationFilter ) ) [ 0 ] ; if ( ! relation ) { throw new Error ( `Unable to find ${ relationName } to set on ${ this . model . name }` ) ; } await entity . $set ( relationName as keyof Entity , relationId ) ; return entity ; } param id param relationName param relationIds param opts async removeRelations < Relation > ( relationName , id , relationIds , opts ? ) { const entity = await this . getById ( id , opts ) ; const relations = await this . getRelations ( relationName , relationIds , opts ?. relationFilter ) ; if ( ! this . foundAllRelations ( relationIds , relations ) ) { throw new Error ( `Unable to find all ${ relationName } to remove from ${ this . model . name }` ) ; } await entity . $remove ( relationName , relationIds ) ; return entity ; } param id param relationName param relationId param opts async removeRelation < Relation > ( relationName , id , relationId , opts ? ) { const entity = await this . getById ( id , opts ) ; const association = this . getAssociation ( relationName ) ; const relation = ( await this . getRelations ( relationName , [ relationId ] , opts ?. relationFilter ) ) [ 0 ] ; if ( ! relation ) { throw new Error ( `Unable to find ${ relationName } to remove from ${ this . model . name }` ) ; } if ( association . isSingleAssociation ) { await entity . $set ( relationName as keyof Entity , null as unknown as string ) ; } else { await entity . $remove ( relationName , relationId ) ; } return entity ; } getRelationQueryBuilder < Relation extends Model > ( model ) { return new FilterQueryBuilder < Relation > ( model ) ; } param RelationClass param entities param relationName param query private async batchQueryRelations < Relation > ( RelationClass , relationName , entities , query ) { const relationEntity = this . getRelationEntity ( relationName ) ; const assembler = AssemblerFactory . getAssembler ( RelationClass , relationEntity ) ; const relationQueryBuilder = this . getRelationQueryBuilder ( relationEntity ) ; const findOptions = relationQueryBuilder . findOptions ( assembler . convertQuery ( query ) ) ; return entities . reduce ( async ( mapPromise , e ) => { const map = await mapPromise ; const relations = await this . ensureIsEntity ( e ) . $get ( relationName as keyof Entity , findOptions ) ; map . set ( e , assembler . convertToDTOs ( relations as unknown as Model [ ] ) ) ; return map ; } , Promise . resolve ( new Map < Entity , Relation [ ] > ( ) ) ) ; } private async batchAggregateRelations < Relation > ( RelationClass , relationName , entities , filter , aggregate ) { const relationEntity = this . getRelationEntity ( relationName ) ; const assembler = AssemblerFactory . getAssembler ( RelationClass , relationEntity ) ; const relationQueryBuilder = this . getRelationQueryBuilder ( relationEntity ) ; const findOptions = relationQueryBuilder . relationAggregateOptions ( assembler . convertQuery ( { filter } ) , assembler . convertAggregateQuery ( aggregate ) ) ; return entities . reduce ( async ( mapPromise , e ) => { const map = await mapPromise ; const results = ( await this . ensureIsEntity ( e ) . $get ( relationName as keyof Entity , findOptions ) ) as Record < string , unknown > [ ] ; const aggResponse = AggregateBuilder . convertToAggregateResponse ( results ) . map ( ( agg ) => assembler . convertAggregateResponse ( agg ) ) ; map . set ( e , aggResponse ) ; return map ; } , Promise . resolve ( new Map < Entity , AggregateResponse < Relation > [ ] > ( ) ) ) ; } private async batchCountRelations < Relation > ( RelationClass , relationName , entities , filter ) { const relationEntity = this . getRelationEntity ( relationName ) ; const assembler = AssemblerFactory . getAssembler ( RelationClass , relationEntity ) ; const relationQueryBuilder = this . getRelationQueryBuilder < Model > ( relationEntity ) ; const findOptions = relationQueryBuilder . countOptions ( assembler . convertQuery ( { filter } ) ) ; return entities . reduce ( async ( mapPromise , e ) => { const map = await mapPromise ; const count = await this . ensureIsEntity ( e ) . $count ( relationName , findOptions ) ; map . set ( e , count ) ; return map ; } , Promise . resolve ( new Map < Entity , number > ( ) ) ) ; } private async batchFindRelations < Relation > ( RelationClass , relationName , dtos , opts ? ) { const relationEntity = this . getRelationEntity ( relationName ) ; const assembler = AssemblerFactory . getAssembler ( RelationClass , relationEntity ) ; const relationQueryBuilder = this . getRelationQueryBuilder ( relationEntity ) ; return dtos . reduce ( async ( mapPromise , e ) => { const map = await mapPromise ; const relation = await this . ensureIsEntity ( e ) . $get ( relationName as keyof Entity , relationQueryBuilder . findOptions ( opts ?? { } ) ) ; if ( relation ) { map . set ( e , assembler . convertToDTO ( relation as unknown as Model ) ) ; } return map ; } , Promise . resolve ( new Map < Entity , Relation | undefined > ( ) ) ) ; } private ensureIsEntity ( e ) { if ( ! ( e instanceof this . model ) ) { return this . model . build ( e ) ; } return e ; } private getAssociation ( relationName ) { const association = this . model . associations [ relationName ] ; if ( ! association ) { throw new Error ( `Unable to find relation ${ relationName } on ${ this . model . name }` ) ; } return association ; } private getRelationEntity ( relationName ) { return this . getAssociation ( relationName ) . target as ModelCtor ; } private getRelations < Relation > ( relationName , ids , filter ? ) { const relationEntity = this . getRelationEntity ( relationName ) ; const relationQueryBuilder = this . getRelationQueryBuilder ( relationEntity ) ; const findOptions = relationQueryBuilder . findByIdOptions ( ids , { filter } ) ; return relationEntity . findAll ( { ... findOptions , attributes : [ ... relationEntity . primaryKeyAttributes ] } ) ; } private foundAllRelations ( relationIds , relations ) { return new Set ( [ ... relationIds ] ) . size === relations . length ; } }