From 90992e1a1dcc4e4e888e5946ab639535932f8f52 Mon Sep 17 00:00:00 2001 From: Doug Martin Date: Sun, 28 Mar 2021 22:31:43 -0600 Subject: [PATCH] feat(typegoose): Update to support new aggregate with groupBy --- .../__tests__/query/aggregate.builder.spec.ts | 98 ++-- .../services/typegoose-query.service.spec.ts | 424 +++++++++++++----- packages/query-typegoose/package.json | 3 +- .../src/query/aggregate.builder.ts | 49 +- .../src/query/filter-query.builder.ts | 4 +- .../src/services/reference-query.service.ts | 14 +- .../src/services/typegoose-query-service.ts | 9 +- 7 files changed, 411 insertions(+), 190 deletions(-) diff --git a/packages/query-typegoose/__tests__/query/aggregate.builder.spec.ts b/packages/query-typegoose/__tests__/query/aggregate.builder.spec.ts index 018986e7a..88d76b5f0 100644 --- a/packages/query-typegoose/__tests__/query/aggregate.builder.spec.ts +++ b/packages/query-typegoose/__tests__/query/aggregate.builder.spec.ts @@ -1,70 +1,72 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { AggregateQuery } from '@nestjs-query/core'; import { TestEntity } from '../__fixtures__/test.entity'; -import { AggregateBuilder, TypegooseAggregate } from '../../src/query'; +import { AggregateBuilder } from '../../src/query'; describe('AggregateBuilder', (): void => { const createAggregateBuilder = () => new AggregateBuilder(); - const assertQuery = (agg: AggregateQuery, expected: TypegooseAggregate): void => { - const actual = createAggregateBuilder().build(agg); - expect(actual).toEqual(expected); - }; - it('should throw an error if no selects are generated', (): void => { expect(() => createAggregateBuilder().build({})).toThrow('No aggregate fields found.'); }); - it('or multiple operators for a single field together', (): void => { - assertQuery( - { - count: ['id', 'stringType'], - avg: ['numberType'], - sum: ['numberType'], - max: ['stringType', 'dateType', 'numberType'], - min: ['stringType', 'dateType', 'numberType'], - }, - { - avg_numberType: { $avg: '$numberType' }, - count_id: { $sum: { $cond: { else: 1, if: { $in: [{ $type: '$_id' }, ['missing', 'null']] }, then: 0 } } }, - count_stringType: { - $sum: { $cond: { else: 1, if: { $in: [{ $type: '$stringType' }, ['missing', 'null']] }, then: 0 } }, - }, - max_dateType: { $max: '$dateType' }, - max_numberType: { $max: '$numberType' }, - max_stringType: { $max: '$stringType' }, - min_dateType: { $min: '$dateType' }, - min_numberType: { $min: '$numberType' }, - min_stringType: { $min: '$stringType' }, - sum_numberType: { $sum: '$numberType' }, + it('should create an aggregate query', (): void => { + const agg: AggregateQuery = { + count: ['id', 'stringType'], + avg: ['numberType'], + sum: ['numberType'], + max: ['stringType', 'dateType', 'numberType'], + min: ['stringType', 'dateType', 'numberType'], + }; + expect(createAggregateBuilder().build(agg)).toEqual({ + _id: null, + avg_numberType: { $avg: '$numberType' }, + count_id: { $sum: { $cond: { else: 1, if: { $in: [{ $type: '$_id' }, ['missing', 'null']] }, then: 0 } } }, + count_stringType: { + $sum: { $cond: { else: 1, if: { $in: [{ $type: '$stringType' }, ['missing', 'null']] }, then: 0 } }, }, - ); + max_dateType: { $max: '$dateType' }, + max_numberType: { $max: '$numberType' }, + max_stringType: { $max: '$stringType' }, + min_dateType: { $min: '$dateType' }, + min_numberType: { $min: '$numberType' }, + min_stringType: { $min: '$stringType' }, + sum_numberType: { $sum: '$numberType' }, + }); }); describe('.convertToAggregateResponse', () => { it('should convert a flat response into an Aggregtate response', () => { - const dbResult = { - count_id: 10, - sum_numberType: 55, - avg_numberType: 5, - max_stringType: 'z', - max_numberType: 10, - min_stringType: 'a', - min_numberType: 1, - }; - expect(AggregateBuilder.convertToAggregateResponse(dbResult)).toEqual({ - count: { id: 10 }, - sum: { numberType: 55 }, - avg: { numberType: 5 }, - max: { stringType: 'z', numberType: 10 }, - min: { stringType: 'a', numberType: 1 }, - }); + const dbResult = [ + { + _id: { group_by_stringType: 'z' }, + count_id: 10, + sum_numberType: 55, + avg_numberType: 5, + max_stringType: 'z', + max_numberType: 10, + min_stringType: 'a', + min_numberType: 1, + }, + ]; + expect(AggregateBuilder.convertToAggregateResponse(dbResult)).toEqual([ + { + groupBy: { stringType: 'z' }, + count: { id: 10 }, + sum: { numberType: 55 }, + avg: { numberType: 5 }, + max: { stringType: 'z', numberType: 10 }, + min: { stringType: 'a', numberType: 1 }, + }, + ]); }); it('should throw an error if a column is not expected', () => { - const dbResult = { - COUNTtestEntityPk: 10, - }; + const dbResult = [ + { + COUNTtestEntityPk: 10, + }, + ]; expect(() => AggregateBuilder.convertToAggregateResponse(dbResult)).toThrow( 'Unknown aggregate column encountered.', ); diff --git a/packages/query-typegoose/__tests__/services/typegoose-query.service.spec.ts b/packages/query-typegoose/__tests__/services/typegoose-query.service.spec.ts index fa501fc59..b0310227d 100644 --- a/packages/query-typegoose/__tests__/services/typegoose-query.service.spec.ts +++ b/packages/query-typegoose/__tests__/services/typegoose-query.service.spec.ts @@ -192,29 +192,100 @@ describe('TypegooseQueryService', () => { min: ['id', 'dateType', 'numberType', 'stringType'], }, ); - return expect(queryResult).toEqual({ - avg: { - numberType: 5.5, - }, - count: { - id: 10, + return expect(queryResult).toEqual([ + { + avg: { + numberType: 5.5, + }, + count: { + id: 10, + }, + max: { + dateType: TEST_ENTITIES[9].dateType, + numberType: 10, + stringType: 'foo9', + id: expect.any(ObjectId), + }, + min: { + dateType: TEST_ENTITIES[0].dateType, + numberType: 1, + stringType: 'foo1', + id: expect.any(ObjectId), + }, + sum: { + numberType: 55, + }, }, - max: { - dateType: TEST_ENTITIES[9].dateType, - numberType: 10, - stringType: 'foo9', - id: expect.any(ObjectId), + ]); + }); + + it('allow aggregates with groupBy', async () => { + const queryService = moduleRef.get(TestEntityService); + const queryResult = await queryService.aggregate( + {}, + { + groupBy: ['boolType'], + count: ['id'], + avg: ['numberType'], + sum: ['numberType'], + max: ['id', 'dateType', 'numberType', 'stringType'], + min: ['id', 'dateType', 'numberType', 'stringType'], }, - min: { - dateType: TEST_ENTITIES[0].dateType, - numberType: 1, - stringType: 'foo1', - id: expect.any(ObjectId), + ); + return expect(queryResult).toEqual([ + { + groupBy: { + boolType: true, + }, + avg: { + numberType: 6, + }, + count: { + id: 5, + }, + max: { + dateType: TEST_ENTITIES[9].dateType, + numberType: 10, + stringType: 'foo8', + id: expect.any(ObjectId), + }, + min: { + dateType: TEST_ENTITIES[1].dateType, + numberType: 2, + stringType: 'foo10', + id: expect.any(ObjectId), + }, + sum: { + numberType: 30, + }, }, - sum: { - numberType: 55, + { + groupBy: { + boolType: false, + }, + avg: { + numberType: 5, + }, + count: { + id: 5, + }, + max: { + dateType: TEST_ENTITIES[8].dateType, + numberType: 9, + stringType: 'foo9', + id: expect.any(ObjectId), + }, + min: { + dateType: TEST_ENTITIES[0].dateType, + numberType: 1, + stringType: 'foo1', + id: expect.any(ObjectId), + }, + sum: { + numberType: 25, + }, }, - }); + ]); }); it('call select with the aggregate columns and return the result with a filter', async () => { @@ -229,29 +300,31 @@ describe('TypegooseQueryService', () => { min: ['id', 'dateType', 'numberType', 'stringType'], }, ); - return expect(queryResult).toEqual({ - avg: { - numberType: 2, - }, - count: { - id: 3, - }, - max: { - dateType: TEST_ENTITIES[2].dateType, - numberType: 3, - stringType: 'foo3', - id: expect.any(ObjectId), - }, - min: { - dateType: TEST_ENTITIES[0].dateType, - numberType: 1, - stringType: 'foo1', - id: expect.any(ObjectId), - }, - sum: { - numberType: 6, + return expect(queryResult).toEqual([ + { + avg: { + numberType: 2, + }, + count: { + id: 3, + }, + max: { + dateType: TEST_ENTITIES[2].dateType, + numberType: 3, + stringType: 'foo3', + id: expect.any(ObjectId), + }, + min: { + dateType: TEST_ENTITIES[0].dateType, + numberType: 1, + stringType: 'foo1', + id: expect.any(ObjectId), + }, + sum: { + numberType: 6, + }, }, - }); + ]); }); }); @@ -774,7 +847,7 @@ describe('TypegooseQueryService', () => { describe('#aggregateRelations', () => { describe('with one entity', () => { - it('call select and return the result', async () => { + it('should return an aggregate', async () => { const queryService = moduleRef.get(TestEntityService); const aggResult = await queryService.aggregateRelations( TestReference, @@ -783,11 +856,32 @@ describe('TypegooseQueryService', () => { { referenceName: { isNot: null } }, { count: ['id'] }, ); - return expect(aggResult).toEqual({ - count: { - id: 3, + return expect(aggResult).toEqual([ + { + count: { + id: 3, + }, }, - }); + ]); + }); + + it('should support groupBy when aggregating relations', async () => { + const queryService = moduleRef.get(TestEntityService); + const aggResult = await queryService.aggregateRelations( + TestReference, + 'testReferences', + TEST_ENTITIES[0], + { referenceName: { isNot: null } }, + { groupBy: ['testEntity'], count: ['id'] }, + ); + return expect(aggResult).toEqual([ + { + groupBy: { testEntity: TEST_ENTITIES[0]._id }, + count: { + id: 3, + }, + }, + ]); }); }); @@ -801,16 +895,18 @@ describe('TypegooseQueryService', () => { { referenceName: { isNot: null } }, { count: ['id'] }, ); - return expect(aggResult).toEqual({ - count: { - id: 3, + return expect(aggResult).toEqual([ + { + count: { + id: 3, + }, }, - }); + ]); }); }); describe('with multiple entities', () => { - it('call select and return the result', async () => { + it('return a relation aggregate for each entity', async () => { const entities = TEST_ENTITIES.slice(0, 3); const queryService = moduleRef.get(TestEntityService); const queryResult = await queryService.aggregateRelations( @@ -830,63 +926,161 @@ describe('TypegooseQueryService', () => { new Map([ [ entities[0], - { - count: { - referenceName: 3, - testEntity: 3, - id: 3, + [ + { + count: { + referenceName: 3, + testEntity: 3, + id: 3, + }, + max: { + referenceName: TEST_REFERENCES[2].referenceName, + testEntity: entities[0]._id, + id: expect.any(ObjectId), + }, + min: { + referenceName: TEST_REFERENCES[0].referenceName, + testEntity: entities[0]._id, + id: expect.any(ObjectId), + }, }, - max: { - referenceName: TEST_REFERENCES[2].referenceName, - testEntity: entities[0]._id, - id: expect.any(ObjectId), - }, - min: { - referenceName: TEST_REFERENCES[0].referenceName, - testEntity: entities[0]._id, - id: expect.any(ObjectId), - }, - }, + ], ], [ entities[1], - { - count: { - referenceName: 3, - testEntity: 3, - id: 3, - }, - max: { - referenceName: TEST_REFERENCES[5].referenceName, - testEntity: entities[1]._id, - id: expect.any(ObjectId), + [ + { + count: { + referenceName: 3, + testEntity: 3, + id: 3, + }, + max: { + referenceName: TEST_REFERENCES[5].referenceName, + testEntity: entities[1]._id, + id: expect.any(ObjectId), + }, + min: { + referenceName: TEST_REFERENCES[3].referenceName, + testEntity: entities[1]._id, + id: expect.any(ObjectId), + }, }, - min: { - referenceName: TEST_REFERENCES[3].referenceName, - testEntity: entities[1]._id, - id: expect.any(ObjectId), - }, - }, + ], ], [ entities[2], - { - count: { - referenceName: 3, - testEntity: 3, - id: 3, + [ + { + count: { + referenceName: 3, + testEntity: 3, + id: 3, + }, + max: { + referenceName: TEST_REFERENCES[8].referenceName, + testEntity: entities[2]._id, + id: expect.any(ObjectId), + }, + min: { + referenceName: TEST_REFERENCES[6].referenceName, + testEntity: entities[2]._id, + id: expect.any(ObjectId), + }, }, - max: { - referenceName: TEST_REFERENCES[8].referenceName, - testEntity: entities[2]._id, - id: expect.any(ObjectId), + ], + ], + ]), + ); + }); + + it('aggregate and group for each entities relation', async () => { + const entities = TEST_ENTITIES.slice(0, 3); + const queryService = moduleRef.get(TestEntityService); + const queryResult = await queryService.aggregateRelations( + TestReference, + 'testReferences', + entities, + { referenceName: { isNot: null } }, + { + groupBy: ['testEntity'], + count: ['id', 'referenceName', 'testEntity'], + min: ['id', 'referenceName', 'testEntity'], + max: ['id', 'referenceName', 'testEntity'], + }, + ); + + expect(queryResult.size).toBe(3); + expect(queryResult).toEqual( + new Map([ + [ + entities[0], + [ + { + groupBy: { testEntity: entities[0]._id }, + count: { + referenceName: 3, + testEntity: 3, + id: 3, + }, + max: { + referenceName: TEST_REFERENCES[2].referenceName, + testEntity: entities[0]._id, + id: expect.any(ObjectId), + }, + min: { + referenceName: TEST_REFERENCES[0].referenceName, + testEntity: entities[0]._id, + id: expect.any(ObjectId), + }, }, - min: { - referenceName: TEST_REFERENCES[6].referenceName, - testEntity: entities[2]._id, - id: expect.any(ObjectId), + ], + ], + [ + entities[1], + [ + { + groupBy: { testEntity: entities[1]._id }, + count: { + referenceName: 3, + testEntity: 3, + id: 3, + }, + max: { + referenceName: TEST_REFERENCES[5].referenceName, + testEntity: entities[1]._id, + id: expect.any(ObjectId), + }, + min: { + referenceName: TEST_REFERENCES[3].referenceName, + testEntity: entities[1]._id, + id: expect.any(ObjectId), + }, }, - }, + ], + ], + [ + entities[2], + [ + { + groupBy: { testEntity: entities[2]._id }, + count: { + referenceName: 3, + testEntity: 3, + id: 3, + }, + max: { + referenceName: TEST_REFERENCES[8].referenceName, + testEntity: entities[2]._id, + id: expect.any(ObjectId), + }, + min: { + referenceName: TEST_REFERENCES[6].referenceName, + testEntity: entities[2]._id, + id: expect.any(ObjectId), + }, + }, + ], ], ]), ); @@ -914,25 +1108,27 @@ describe('TypegooseQueryService', () => { new Map([ [ entities[0], - { - count: { - referenceName: 3, - testEntity: 3, - id: 3, - }, - max: { - referenceName: TEST_REFERENCES[2].referenceName, - testEntity: entities[0]._id, - id: expect.any(ObjectId), - }, - min: { - referenceName: TEST_REFERENCES[0].referenceName, - testEntity: entities[0]._id, - id: expect.any(ObjectId), + [ + { + count: { + referenceName: 3, + testEntity: 3, + id: 3, + }, + max: { + referenceName: TEST_REFERENCES[2].referenceName, + testEntity: entities[0]._id, + id: expect.any(ObjectId), + }, + min: { + referenceName: TEST_REFERENCES[0].referenceName, + testEntity: entities[0]._id, + id: expect.any(ObjectId), + }, }, - }, + ], ], - [entities[1], {}], + [entities[1], []], ]), ); }); diff --git a/packages/query-typegoose/package.json b/packages/query-typegoose/package.json index 58340bfc6..671a6ef3d 100644 --- a/packages/query-typegoose/package.json +++ b/packages/query-typegoose/package.json @@ -21,7 +21,8 @@ "@nestjs-query/core": "0.24.4", "lodash.escaperegexp": "^4.1.2", "lodash.merge": "^4.6.2", - "tslib": "^2.1.0" + "tslib": "^2.1.0", + "camel-case": "^4.1.2" }, "peerDependencies": { "@nestjs/common": "^7.0.0", diff --git a/packages/query-typegoose/src/query/aggregate.builder.ts b/packages/query-typegoose/src/query/aggregate.builder.ts index 6cf566730..d73463aa3 100644 --- a/packages/query-typegoose/src/query/aggregate.builder.ts +++ b/packages/query-typegoose/src/query/aggregate.builder.ts @@ -1,6 +1,7 @@ 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 { @@ -10,14 +11,11 @@ enum AggregateFuncs { MAX = 'max', MIN = 'min', } +type Aggregate = Record>; +type Group = { _id: Record | null }; +export type TypegooseGroupAndAggregate = (Aggregate & Group) | Record; -export type TypegooseAggregate = { - [k: string]: { - [o: string]: unknown; - }; -}; - -const AGG_REGEXP = /(avg|sum|count|max|min)_(.*)/; +const AGG_REGEXP = /(avg|sum|count|max|min|group_by)_(.*)/; /** * @internal @@ -25,14 +23,24 @@ const AGG_REGEXP = /(avg|sum|count|max|min)_(.*)/; */ export class AggregateBuilder { // eslint-disable-next-line @typescript-eslint/no-shadow - static convertToAggregateResponse({ _id, ...response }: Record): AggregateResponse { + static convertToAggregateResponse(aggregates: Record[]): AggregateResponse[] { + return aggregates.map(({ _id, ...response }) => { + return { ...this.extractResponse(_id as Record), ...this.extractResponse(response) }; + }); + } + + // eslint-disable-next-line @typescript-eslint/no-shadow + private static extractResponse(response?: Record): AggregateResponse { + if (!response) { + return {}; + } return Object.keys(response).reduce((agg, resultField: string) => { const matchResult = AGG_REGEXP.exec(resultField); if (!matchResult) { throw new Error('Unknown aggregate column encountered.'); } const [matchedFunc, matchedFieldName] = matchResult.slice(1); - const aggFunc = matchedFunc.toLowerCase() as keyof AggregateResponse; + const aggFunc = camelCase(matchedFunc.toLowerCase()) as keyof AggregateResponse; const fieldName = matchedFieldName as keyof Entity; const aggResult = agg[aggFunc] || {}; return { @@ -47,25 +55,25 @@ export class AggregateBuilder { * Builds a aggregate SELECT clause from a aggregate. * @param aggregate - the aggregates to select. */ - build(aggregate: AggregateQuery>): TypegooseAggregate { - const query = { + build(aggregate: AggregateQuery>): TypegooseGroupAndAggregate { + const aggSelect: Aggregate = { ...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(query).length) { + if (!Object.keys(aggSelect).length) { throw new BadRequestException('No aggregate fields found.'); } - return query; + return { ...aggSelect, _id: this.createGroupBySelect(aggregate.groupBy) } as TypegooseGroupAndAggregate; } - private createAggSelect(func: AggregateFuncs, fields?: (keyof DocumentType)[]): TypegooseAggregate { + private createAggSelect(func: AggregateFuncs, fields?: (keyof DocumentType)[]): Aggregate { if (!fields) { return {}; } - return fields.reduce((agg: TypegooseAggregate, field) => { + return fields.reduce((agg: Aggregate, field) => { const aggAlias = `${func}_${field as string}`; const fieldAlias = `$${getSchemaKey(String(field))}`; if (func === 'count') { @@ -85,4 +93,15 @@ export class AggregateBuilder { return { ...agg, [aggAlias]: { [`$${func}`]: fieldAlias } }; }, {}); } + + private createGroupBySelect(fields?: (keyof DocumentType)[]): Record | null { + if (!fields) { + return null; + } + return fields.reduce((id: Record, field) => { + const aggAlias = `group_by_${field as string}`; + const fieldAlias = `$${getSchemaKey(String(field))}`; + return { ...id, [aggAlias]: fieldAlias }; + }, {}); + } } diff --git a/packages/query-typegoose/src/query/filter-query.builder.ts b/packages/query-typegoose/src/query/filter-query.builder.ts index 32177e60a..718b6d0bd 100644 --- a/packages/query-typegoose/src/query/filter-query.builder.ts +++ b/packages/query-typegoose/src/query/filter-query.builder.ts @@ -1,7 +1,7 @@ import { AggregateQuery, Filter, Query, SortDirection, SortField } from '@nestjs-query/core'; import { FilterQuery } from 'mongoose'; import { DocumentType } from '@typegoose/typegoose'; -import { AggregateBuilder, TypegooseAggregate } from './aggregate.builder'; +import { AggregateBuilder, TypegooseGroupAndAggregate } from './aggregate.builder'; import { getSchemaKey } from './helpers'; import { WhereBuilder } from './where.builder'; @@ -14,7 +14,7 @@ type TypegooseQuery = { type TypegooseAggregateQuery = { filterQuery: FilterQuery; - aggregate: TypegooseAggregate; + aggregate: TypegooseGroupAndAggregate; }; /** * @internal diff --git a/packages/query-typegoose/src/services/reference-query.service.ts b/packages/query-typegoose/src/services/reference-query.service.ts index 74bb8ac08..726e2da86 100644 --- a/packages/query-typegoose/src/services/reference-query.service.ts +++ b/packages/query-typegoose/src/services/reference-query.service.ts @@ -41,7 +41,7 @@ export abstract class ReferenceQueryService { entities: DocumentType[], filter: Filter, aggregate: AggregateQuery, - ): Promise, AggregateResponse>>; + ): Promise, AggregateResponse>[]>>; aggregateRelations( RelationClass: Class, @@ -49,7 +49,7 @@ export abstract class ReferenceQueryService { dto: DocumentType, filter: Filter, aggregate: AggregateQuery, - ): Promise>>; + ): Promise>[]>; async aggregateRelations( RelationClass: Class, @@ -58,7 +58,7 @@ export abstract class ReferenceQueryService { filter: Filter, aggregateQuery: AggregateQuery, ): Promise< - AggregateResponse> | Map, AggregateResponse>> + AggregateResponse>[] | Map, AggregateResponse>[]> > { this.checkForReference('AggregateRelations', relationName); const relationModel = this.getReferenceModel(relationName); @@ -68,21 +68,21 @@ export abstract class ReferenceQueryService { const map = await mapPromise; const refs = await this.aggregateRelations(RelationClass, relationName, entity, filter, aggregateQuery); return map.set(entity, refs); - }, Promise.resolve(new Map, AggregateResponse>>())); + }, Promise.resolve(new Map, AggregateResponse>[]>())); } const assembler = AssemblerFactory.getAssembler(RelationClass, this.getReferenceEntity(relationName)); const refFilter = this.getReferenceFilter(relationName, dto, assembler.convertQuery({ filter }).filter); if (!refFilter) { - return {}; + return []; } const { filterQuery, aggregate } = referenceQueryBuilder.buildAggregateQuery( assembler.convertAggregateQuery(aggregateQuery), refFilter, ); - const [aggResult] = (await relationModel + const aggResult = (await relationModel .aggregate>([{ $match: filterQuery }, { $group: { _id: null, ...aggregate } }]) .exec()) as Record[]; - return aggResult ? AggregateBuilder.convertToAggregateResponse(aggResult) : {}; + return AggregateBuilder.convertToAggregateResponse(aggResult); } countRelations( diff --git a/packages/query-typegoose/src/services/typegoose-query-service.ts b/packages/query-typegoose/src/services/typegoose-query-service.ts index 5d1973919..50b230d2a 100644 --- a/packages/query-typegoose/src/services/typegoose-query-service.ts +++ b/packages/query-typegoose/src/services/typegoose-query-service.ts @@ -52,13 +52,16 @@ export class TypegooseQueryService return entities; } - async aggregate(filter: Filter, aggregateQuery: AggregateQuery): Promise> { + async aggregate( + filter: Filter, + aggregateQuery: AggregateQuery, + ): Promise[]> { const { aggregate, filterQuery } = this.filterQueryBuilder.buildAggregateQuery(aggregateQuery, filter); const aggResult = (await this.Model.aggregate>([ { $match: filterQuery }, - { $group: { _id: null, ...aggregate } }, + { $group: aggregate }, ]).exec()) as Record[]; - return AggregateBuilder.convertToAggregateResponse(aggResult[0]); + return AggregateBuilder.convertToAggregateResponse(aggResult); } count(filter: Filter): Promise {