import { AggregateQuery , AggregateResponse } from '@nestjs-query/core' ; import { BadRequestException } from '@nestjs/common' ; import { DocumentType } from '@typegoose/typegoose' ; import { camelCase } from 'camel-case' ; import { getSchemaKey } from './helpers' ; enum AggregateFuncs { AVG = 'avg' , SUM = 'sum' , COUNT = 'count' , MAX = 'max' , MIN = 'min' } type Aggregate = Record < string , Record < string , unknown > > ; type Group = { _id : Record < string , string > | null ; } ; export type TypegooseGroupAndAggregate = Aggregate & Group ; const AGG_REGEXP = /(avg|sum|count|max|min|group_by)_(.*)/ ; internal export class AggregateBuilder < Entity > { static convertToAggregateResponse < Entity > ( aggregates ) { return aggregates . map ( ( { _id , ... response } ) => { return { ... this . extractResponse ( _id as Record < string , unknown > ) , ... this . extractResponse ( response ) } ; } ) ; } private static extractResponse < Entity > ( response ? ) { if ( ! response ) { return { } ; } return Object . keys ( response ) . reduce ( ( agg , resultField ) => { const matchResult = AGG_REGEXP . exec ( resultField ) ; if ( ! matchResult ) { throw new Error ( 'Unknown aggregate column encountered.' ) ; } const [ matchedFunc , matchedFieldName ] = matchResult . slice ( 1 ) ; const aggFunc = camelCase ( matchedFunc . toLowerCase ( ) ) as keyof AggregateResponse < Entity > ; const fieldName = matchedFieldName as keyof Entity ; const aggResult = agg [ aggFunc ] || { } ; return { ... agg , [ aggFunc ] : { ... aggResult , [ fieldName ] : response [ resultField ] } , } ; } , { } as AggregateResponse < Entity > ) ; } param aggregate build ( aggregate ) { const aggSelect = { ... this . createAggSelect ( AggregateFuncs . COUNT , aggregate . count ) , ... this . createAggSelect ( AggregateFuncs . SUM , aggregate . sum ) , ... this . createAggSelect ( AggregateFuncs . AVG , aggregate . avg ) , ... this . createAggSelect ( AggregateFuncs . MAX , aggregate . max ) , ... this . createAggSelect ( AggregateFuncs . MIN , aggregate . min ) , } ; if ( ! Object . keys ( aggSelect ) . length ) { throw new BadRequestException ( 'No aggregate fields found.' ) ; } return { ... aggSelect , _id : this . createGroupBySelect ( aggregate . groupBy ) } as TypegooseGroupAndAggregate ; } private createAggSelect ( func , fields ? ) { if ( ! fields ) { return { } ; } return fields . reduce ( ( agg , field ) => { const aggAlias = `${ func }_${ field as string }` ; const fieldAlias = `$${ getSchemaKey ( String ( field ) ) }` ; if ( func === 'count' ) { return { ... agg , [ aggAlias ] : { $sum : { $cond : { if : { $in : [ { $type : fieldAlias } , [ 'missing' , 'null' ] ] } , then : 0 , else : 1 , } , } , } , } ; } return { ... agg , [ aggAlias ] : { [ `$${ func }` ] : fieldAlias } } ; } , { } ) ; } private createGroupBySelect ( fields ? ) { if ( ! fields ) { return null ; } return fields . reduce ( ( id , field ) => { const aggAlias = this . getGroupByAlias ( field ) ; const fieldAlias = `$${ getSchemaKey ( String ( field ) ) }` ; return { ... id , [ aggAlias ] : fieldAlias } ; } , { } ) ; } getGroupBySelects ( fields ? ) { if ( ! fields ) { return undefined ; } return ( fields ?? [ ] ) . map ( ( f ) => `_id.${ this . getGroupByAlias ( f ) }` ) ; } private getGroupByAlias ( field ) { return `group_by_${ field as string }` ; } }