import { Query , Filter , AggregateQuery , AggregateResponse , FindByIdOptions , GetByIdOptions , DeepPartial , DeleteManyResponse , DeleteOneOptions , UpdateManyResponse , UpdateOneOptions , QueryService , } from '@nestjs-query/core' ; import { Base } from '@typegoose/typegoose/lib/defaultClasses' ; import { ReturnModelType , DocumentType , mongoose } from '@typegoose/typegoose' ; import { NotFoundException } from '@nestjs/common' ; import { ReferenceQueryService } from './reference-query.service' ; import { AggregateBuilder , FilterQueryBuilder } from '../query' ; import { UpdateArrayQuery } from '../typegoose-types.helper' ; export interface TypegooseQueryServiceOpts { toObjectOptions ? : mongoose . ToObjectOptions ; } export class TypegooseQueryService < Entity extends Base > extends ReferenceQueryService < Entity > implements QueryService < Entity > { constructor ( readonly Model , readonly filterQueryBuilder = new FilterQueryBuilder ( Model ) ) { super ( Model ) ; } example param query async query ( query ) { const { filterQuery , options } = this . filterQueryBuilder . buildQuery ( query ) ; const entities = await this . Model . find ( filterQuery , { } , options ) . exec ( ) ; return entities ; } async aggregate ( filter , aggregateQuery ) { const { aggregate , filterQuery , options } = this . filterQueryBuilder . buildAggregateQuery ( aggregateQuery , filter ) ; const aggPipeline = [ { $match : filterQuery } , { $group : aggregate } ] ; if ( options . sort ) { aggPipeline . push ( { $sort : options . sort ?? { } } ) ; } const aggResult = ( await this . Model . aggregate < Record < string , unknown > > ( aggPipeline ) . exec ( ) ) as Record < string , unknown > [ ] ; return AggregateBuilder . convertToAggregateResponse ( aggResult ) ; } count ( filter ) { const filterQuery = this . filterQueryBuilder . buildFilterQuery ( filter ) ; return this . Model . countDocuments ( filterQuery ) . exec ( ) ; } example param id param opts async findById ( id , opts ? ) { const filterQuery = this . filterQueryBuilder . buildIdFilterQuery ( id , opts ?. filter ) ; const doc = await this . Model . findOne ( filterQuery ) ; if ( ! doc ) { return undefined ; } return doc ; } example param id param opts async getById ( id , opts ? ) { const doc = await this . findById ( id , opts ) ; if ( ! doc ) { throw new NotFoundException ( `Unable to find ${ this . Model . modelName } with id: ${ id }` ) ; } return doc ; } example param record async createOne ( record ) { this . ensureIdIsNotPresent ( record ) ; const doc = await this . Model . create ( record ) ; return doc ; } example param records async createMany ( records ) { records . forEach ( ( r ) => this . ensureIdIsNotPresent ( r ) ) ; const entities = await this . Model . create ( records ) ; return entities ; } example param id param update param opts async updateOne ( id , update , opts ? ) { this . ensureIdIsNotPresent ( update ) ; const filterQuery = this . filterQueryBuilder . buildIdFilterQuery ( id , opts ?. filter ) ; const doc = await this . Model . findOneAndUpdate ( filterQuery , this . getUpdateQuery ( update as DocumentType < Entity > ) , { new : true , } ) ; if ( ! doc ) { throw new NotFoundException ( `Unable to find ${ this . Model . modelName } with id: ${ id }` ) ; } return doc ; } example param update param filter async updateMany ( update , filter ) { this . ensureIdIsNotPresent ( update ) ; const filterQuery = this . filterQueryBuilder . buildFilterQuery ( filter ) ; const res = await this . Model . updateMany ( filterQuery , this . getUpdateQuery ( update as DocumentType < Entity > ) ) . exec ( ) ; return { updatedCount : res . nModified || 0 } ; } example param id param opts async deleteOne ( id , opts ? ) { const filterQuery = this . filterQueryBuilder . buildIdFilterQuery ( id , opts ?. filter ) ; const doc = await this . Model . findOneAndDelete ( filterQuery ) ; if ( ! doc ) { throw new NotFoundException ( `Unable to find ${ this . Model . modelName } with id: ${ id }` ) ; } return doc ; } example param filter async deleteMany ( filter ) { const filterQuery = this . filterQueryBuilder . buildFilterQuery ( filter ) ; const res = await this . Model . deleteMany ( filterQuery ) . exec ( ) ; return { deletedCount : res . deletedCount || 0 } ; } private ensureIdIsNotPresent ( e ) { if ( Object . entries ( e ) . find ( ( [ k , v ] ) => ( k === 'id' || k === '_id' ) && typeof v !== `undefined` ) ) { throw new Error ( 'Id cannot be specified when updating or creating' ) ; } } private getUpdateQuery ( entity ) { if ( entity instanceof this . Model ) { return entity . modifiedPaths ( ) . reduce ( ( update , k ) => ( { ... update , [ k ] : entity . get ( k ) } ) , { } ) ; } const arrayUpdateQuery = this . buildArrayUpdateQuery ( entity as DeepPartial < Entity > ) ; return { ... entity , ... arrayUpdateQuery } as mongoose . UpdateQuery < DocumentType < Entity > > ; } private buildArrayUpdateQuery ( entity ) { let query = { $addToSet : { } , $pull : { } , } as UpdateArrayQuery < Entity > ; Object . keys ( entity ) . forEach ( ( key ) => { if ( this . Model . schema . path ( key ) instanceof mongoose . Schema . Types . Array && typeof entity [ key as keyof Entity ] === 'object' ) { const convert = entity [ key as keyof Entity ] as unknown as { push : Entity [ ] ; pull : Entity [ ] ; } ; if ( Object . prototype . hasOwnProperty . call ( convert , 'push' ) ) { query . $addToSet [ key ] = { $each : convert . push } ; } if ( Object . prototype . hasOwnProperty . call ( convert , 'pull' ) ) { query . $pull [ key ] = { } ; convert . pull . forEach ( ( item , index ) => { Object . keys ( item ) . forEach ( ( innerKey ) => { if ( query . $pull [ key ] [ innerKey ] !== undefined ) { query . $pull [ key ] [ innerKey ] . $in . push ( convert . pull [ index ] [ innerKey as keyof Entity ] ) ; } else { query . $pull [ key ] [ innerKey ] = { $in : [ convert . pull [ index ] [ innerKey as keyof Entity ] ] } ; } } ) ; } ) ; } if ( Object . prototype . hasOwnProperty . call ( entity [ key as keyof Entity ] , 'push' ) || Object . prototype . hasOwnProperty . call ( entity [ key as keyof Entity ] , 'pull' ) ) { delete entity [ key as keyof Entity ] ; } } } ) ; return query ; } }