Skip to content

Commit

Permalink
feat(query-graphql): Look ahead totalCount
Browse files Browse the repository at this point in the history
Fixes #137
  • Loading branch information
TriPSs committed Jun 24, 2023
1 parent ad451bb commit e4713a9
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 77 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
import { GqlExecutionContext } from '@nestjs/graphql'

import type { Class, SelectRelation } from '@ptc-org/nestjs-query-core'
import type { GraphQLResolveInfo as ResolveInfo } from 'graphql'

import { QueryResolveTree, simplifyResolveInfo } from './graphql-resolve-info.utils'
import { createLookAheadInfo, QueryResolveTree, simplifyResolveInfo } from './graphql-resolve-info.utils'
import { getRelationsDescriptors } from './relation.decorator'

export interface GraphQLResolveInfoResult<InfoDTO, RelationsDTO = InfoDTO> {
info?: QueryResolveTree<InfoDTO>
relations?: SelectRelation<RelationsDTO>[]
}

/**
* @internal this implementation is not final and subjected to change! Use at own risk!
*/
export function GraphQLResolveInfo<DTO>(simplify = true): ParameterDecorator {
return createParamDecorator((data: unknown, ctx: ExecutionContext): QueryResolveTree<DTO> | ResolveInfo => {
const resolveInfo = GqlExecutionContext.create(ctx).getInfo<ResolveInfo>()
export const GraphQLResultInfo = <DTO>(DTOClass: Class<DTO>): ParameterDecorator => {
// Get all relations that have look ahead enabled
const relations = getRelationsDescriptors(DTOClass).filter(
(relation) => relation.relationOpts.enableLookAhead && !relation.isMany
)

if (simplify) {
return simplifyResolveInfo<DTO>(resolveInfo)
}
return createParamDecorator((data: unknown, ctx: ExecutionContext): GraphQLResolveInfoResult<DTO> => {
const info = GqlExecutionContext.create(ctx).getInfo<ResolveInfo>()
const simplifiedInfo = simplifyResolveInfo<DTO>(info)

return resolveInfo
return {
info: simplifiedInfo,
relations: relations.length === 0 ? [] : createLookAheadInfo<DTO>(relations, simplifiedInfo)
}
})()
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
} from 'graphql'

import type { CursorConnectionType, OffsetConnectionType } from '../types'
import type { Query } from '@ptc-org/nestjs-query-core'
import type { RelationDescriptor } from './relation.decorator'
import type { Query, SelectRelation } from '@ptc-org/nestjs-query-core'
import type { GraphQLCompositeType, GraphQLResolveInfo as ResolveInfo, SelectionNode } from 'graphql'

type QueryResolveFields<DTO> = {
Expand Down Expand Up @@ -147,3 +148,21 @@ export function simplifyResolveInfo<DTO>(resolveInfo: ResolveInfo): QueryResolve

return simpleInfo as QueryResolveTree<DTO>
}

export function createLookAheadInfo<DTO>(
relations: RelationDescriptor<unknown>[],
simpleResolveInfo: QueryResolveTree<DTO>
): SelectRelation<DTO>[] {
return relations
.map((relation): SelectRelation<DTO> | boolean => {
if (relation.name in simpleResolveInfo.fields) {
return {
name: relation.name,
query: (simpleResolveInfo.fields[relation.name] as QueryResolveTree<DTO>).args || {}
}
}

return false
})
.filter(Boolean) as SelectRelation<DTO>[]
}
1 change: 0 additions & 1 deletion packages/query-graphql/src/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export {
FilterableFieldOptions,
getFilterableFields
} from './filterable-field.decorator'
export * from './graphql-look-ahead-relations.decorator'
export * from './graphql-resolve-info.decorator'
export * from './hook.decorator'
export * from './hook-args.decorator'
Expand Down
31 changes: 21 additions & 10 deletions packages/query-graphql/src/resolvers/read.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import omit from 'lodash.omit'

import { OperationGroup } from '../auth'
import { getDTONames } from '../common'
import { AuthorizerFilter, GraphQLLookAheadRelations, HookArgs, ResolverQuery } from '../decorators'
import { AuthorizerFilter, GraphQLResolveInfoResult, GraphQLResultInfo, HookArgs, ResolverQuery } from '../decorators'
import { HookTypes } from '../hooks'
import { AuthorizerInterceptor, HookInterceptor } from '../interceptors'
import {
Expand Down Expand Up @@ -35,10 +35,14 @@ export interface ReadResolver<DTO, PS extends PagingStrategies, QS extends Query
queryMany(
query: QueryType<DTO, PagingStrategies>,
authorizeFilter?: Filter<DTO>,
selectRelations?: SelectRelation<DTO>[]
resolveInfo?: GraphQLResolveInfoResult<InferConnectionTypeFromStrategy<DTO, PS>, DTO>
): Promise<InferConnectionTypeFromStrategy<DTO, PS>>

findById(id: FindOneArgsType, authorizeFilter?: Filter<DTO>, selectRelations?: SelectRelation<DTO>[]): Promise<DTO | undefined>
findById(
id: FindOneArgsType,
authorizeFilter?: Filter<DTO>,
resolveInfo?: GraphQLResolveInfoResult<DTO>
): Promise<DTO | undefined>
}

/**
Expand Down Expand Up @@ -85,13 +89,13 @@ export const Readable =
many: false
})
authorizeFilter?: Filter<DTO>,
@GraphQLLookAheadRelations(DTOClass)
relations?: SelectRelation<DTO>[]
@GraphQLResultInfo(DTOClass)
resolveInfo?: GraphQLResolveInfoResult<DTO>
): Promise<DTO> {
return this.service.getById(input.id, {
filter: authorizeFilter,
withDeleted: opts?.one?.withDeleted,
relations
relations: resolveInfo.relations
})
}

Expand All @@ -109,13 +113,20 @@ export const Readable =
many: true
})
authorizeFilter?: Filter<DTO>,
@GraphQLLookAheadRelations(DTOClass)
relations?: SelectRelation<DTO>[]
@GraphQLResultInfo(DTOClass)
resolveInfo?: GraphQLResolveInfoResult<InferConnectionTypeFromStrategy<DTO, ExtractPagingStrategy<DTO, ReadOpts>>, DTO>
): Promise<InstanceType<typeof ConnectionType>> {
return ConnectionType.createFromPromise(
(q) => this.service.query(q),
mergeQuery(query, { filter: authorizeFilter, relations }),
(filter) => this.service.count(filter)
mergeQuery(query, { filter: authorizeFilter, relations: resolveInfo.relations }),
(filter) => {
// If the total count is fetched, then query the service
if ('totalCount' in resolveInfo.info.fields) {
return this.service.count(filter)
}

return Promise.resolve(0)
}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ExecutionContext } from '@nestjs/common'
import { Args, ArgsType, Context, Parent, Resolver } from '@nestjs/graphql'
import { Class, Filter, mergeQuery, QueryService, SelectRelation } from '@ptc-org/nestjs-query-core'
import { Class, Filter, mergeQuery, QueryService } from '@ptc-org/nestjs-query-core'

import { OperationGroup } from '../../auth'
import { getDTONames } from '../../common'
import { GraphQLLookAheadRelations, RelationAuthorizerFilter, ResolverField } from '../../decorators'
import { GraphQLResolveInfoResult, GraphQLResultInfo, RelationAuthorizerFilter, ResolverField } from '../../decorators'
import { InjectDataLoaderConfig } from '../../decorators/inject-dataloader-config.decorator'
import { AuthorizerInterceptor } from '../../interceptors'
import { CountRelationsLoader, DataLoaderFactory, FindRelationsLoader, QueryRelationsLoader } from '../../loader'
Expand Down Expand Up @@ -49,8 +49,8 @@ const ReadOneRelationMixin =
many: false
})
authFilter?: Filter<Relation>,
@GraphQLLookAheadRelations(DTOClass)
relations?: SelectRelation<Relation>[],
@GraphQLResultInfo(DTOClass)
resolveInfo?: GraphQLResolveInfoResult<Relation>,
@InjectDataLoaderConfig()
dataLoaderConfig?: DataLoaderOptions
): Promise<Relation | undefined> {
Expand All @@ -66,7 +66,7 @@ const ReadOneRelationMixin =
).load({
dto,
filter: authFilter,
relations
relations: resolveInfo.relations
})
}
}
Expand Down Expand Up @@ -119,8 +119,8 @@ const ReadManyRelationMixin =
many: true
})
relationFilter?: Filter<Relation>,
@GraphQLLookAheadRelations(relationDTO)
relations?: SelectRelation<Relation>[],
@GraphQLResultInfo(DTOClass)
resolveInfo?: GraphQLResolveInfoResult<Relation>,
@InjectDataLoaderConfig()
dataLoaderConfig?: DataLoaderOptions
): Promise<InstanceType<typeof CT>> {
Expand All @@ -141,8 +141,15 @@ const ReadManyRelationMixin =

return CT.createFromPromise(
(query) => relationLoader.load({ dto, query }),
mergeQuery(relationQuery, { filter: relationFilter, relations }),
(filter) => relationCountLoader.load({ dto, filter })
mergeQuery(relationQuery, { filter: relationFilter, relations: resolveInfo.relations }),
(filter) => {
// If the total count is fetched, than query the service
if ('totalCount' in resolveInfo.info.fields) {
return relationCountLoader.load({ dto, filter })
}

return Promise.resolve(0)
}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,22 @@ export function getOrCreateOffsetConnectionType<DTO>(
this.totalCountFn = totalCountFn ?? DEFAULT_COUNT
}

@Field(() => PIT, { description: 'Paging information' })
@Field(() => PIT, {
description: 'Paging information'
})
pageInfo!: OffsetPageInfoType

@Field(() => [TItemClass], { description: 'Array of nodes.' })
@Field(() => [TItemClass], {
description: 'Array of nodes.'
})
nodes!: DTO[]

@SkipIf(() => !opts.enableTotalCount, Field(() => Int, { description: 'Fetch total count of records' }))
@SkipIf(
() => !opts.enableTotalCount,
Field(() => Int, {
description: 'Fetch total count of records'
})
)
get totalCount(): Promise<number> {
return this.totalCountFn()
}
Expand Down

0 comments on commit e4713a9

Please sign in to comment.