Skip to content

Commit

Permalink
Merge pull request #288 from ikoenigsknecht/feature/findAndCount
Browse files Browse the repository at this point in the history
feat(service): Add findAndCount to BaseService
  • Loading branch information
goldcaddy77 authored Apr 8, 2020
2 parents 0de5f91 + 8728725 commit 11ae817
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 5 deletions.
45 changes: 42 additions & 3 deletions src/core/BaseService.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { validate } from 'class-validator';
import { ArgumentValidationError } from 'type-graphql';
import { DeepPartial, EntityManager, getRepository, Repository } from 'typeorm';
import { DeepPartial, EntityManager, getRepository, Repository, SelectQueryBuilder } from 'typeorm';
import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';

import { StandardDeleteResponse } from '../tgql';
import { addQueryBuilderWhereItem } from '../torm';

import { BaseModel } from '..';
import { BaseModel, ConnectionResult } from '..';
import { StringMap, WhereInput } from './types';

interface BaseOptions {
Expand Down Expand Up @@ -51,13 +51,52 @@ export class BaseService<E extends BaseModel> {
this.klass = this.repository.metadata.name.toLowerCase();
}

getPageInfo(limit: number, offset: number, totalCount: number) {
return {
hasNextPage: totalCount > offset + limit,
hasPreviousPage: offset > 0,
limit,
offset,
totalCount
};
}

async find<W extends WhereInput>(
where?: any,
orderBy?: string,
limit?: number,
offset?: number,
fields?: string[]
): Promise<E[]> {
return this.buildFindQuery<W>(where, orderBy, limit, offset, fields).getMany();
}

async findConnection<W extends WhereInput>(
where?: any,
orderBy?: string,
limit?: number,
offset?: number,
fields?: string[]
): Promise<ConnectionResult<E>> {
const qb = this.buildFindQuery<W>(where, orderBy, limit, offset, fields);
const [nodes, totalCount] = await qb.getManyAndCount();
// TODO: FEATURE - make the default limit configurable
limit = limit ?? 50;
offset = offset ?? 0;

return {
nodes,
pageInfo: this.getPageInfo(limit, offset, totalCount)
};
}

private buildFindQuery<W extends WhereInput>(
where?: any,
orderBy?: string,
limit?: number,
offset?: number,
fields?: string[]
): SelectQueryBuilder<E> {
let qb = this.manager.createQueryBuilder<E>(this.entityClass, this.klass);

if (limit) {
Expand Down Expand Up @@ -124,7 +163,7 @@ export class BaseService<E extends BaseModel> {
});
}

return qb.getMany();
return qb;
}

async findOne<W extends Partial<E>>(where: W): Promise<E> {
Expand Down
26 changes: 26 additions & 0 deletions src/decorators/Fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,29 @@ export function Fields(): ParameterDecorator {
return scalars;
});
}

export function NestedFields(): ParameterDecorator {
return createParamDecorator(({ info }) => {
// This object will be of the form:
// rawFields {
// baseField: {},
// association: { subField: "foo"}
// }
// We need to pull out items with subFields
const rawFields = graphqlFields(info);
const output: any = { scalars: [] };

for (const fieldKey in rawFields) {
if (Object.keys(rawFields[fieldKey]).length === 0) {
output.scalars.push(fieldKey);
} else {
const subFields = rawFields[fieldKey];
output[fieldKey] = Object.keys(subFields).filter(subKey => {
return Object.keys(subFields[subKey]).length === 0;
});
}
}

return output;
});
}
14 changes: 14 additions & 0 deletions src/test/functional/__snapshots__/schema.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ type Dish implements BaseGraphQLObject {
kitchenSinkId: String!
}
type DishConnection {
nodes: [Dish!]!
pageInfo: PageInfo!
}
input DishCreateInput {
name: String!
kitchenSinkId: ID!
Expand Down Expand Up @@ -377,8 +382,17 @@ type Mutation {
deleteKitchenSink(where: KitchenSinkWhereUniqueInput!): StandardDeleteResponse!
}
type PageInfo {
limit: Float!
offset: Float!
totalCount: Float!
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}
type Query {
dishes(offset: Int, limit: Int = 50, where: DishWhereInput, orderBy: DishOrderByInput): [Dish!]!
dishConnection(offset: Int, limit: Int = 50, where: DishWhereInput, orderBy: DishOrderByInput): DishConnection!
dish(where: DishWhereUniqueInput!): Dish!
kitchenSinks(offset: Int, limit: Int = 50, where: KitchenSinkWhereInput, orderBy: KitchenSinkOrderByInput): [KitchenSink!]!
kitchenSink(where: KitchenSinkWhereUniqueInput!): KitchenSink!
Expand Down
11 changes: 11 additions & 0 deletions src/test/functional/__snapshots__/server.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1372,3 +1372,14 @@ Array [
},
]
`;

exports[`server queries for dishes with pagination 1`] = `
Array [
Object {
"kitchenSink": Object {
"emailField": "[email protected]",
},
"name": "Dish 0",
},
]
`;
31 changes: 31 additions & 0 deletions src/test/functional/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,37 @@ describe('server', () => {
expect(firstResult.dishes.length).toEqual(20);
});

test('queries for dishes with pagination', async () => {
expect.assertions(6);
const { nodes, pageInfo } = await binding.query.dishConnection(
{ offset: 0, orderBy: 'createdAt_ASC', limit: 1 },
`{
nodes {
name
kitchenSink {
emailField
}
}
pageInfo {
limit
offset
totalCount
hasNextPage
hasPreviousPage
}
}`
);

console.log('test', nodes, pageInfo);

expect(nodes).toMatchSnapshot();
expect(pageInfo.offset).toEqual(0);
expect(pageInfo.limit).toEqual(1);
expect(pageInfo.hasNextPage).toEqual(true);
expect(pageInfo.hasPreviousPage).toEqual(false);
expect(pageInfo.totalCount).toEqual(20);
});

test('throws errors when given bad input on a single create', async done => {
expect.assertions(1);

Expand Down
14 changes: 14 additions & 0 deletions src/test/generated/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as schema from './schema.graphql'

export interface Query {
dishes: <T = Array<Dish>>(args: { offset?: Int | null, limit?: Int | null, where?: DishWhereInput | null, orderBy?: DishOrderByInput | null }, info?: GraphQLResolveInfo | string, options?: Options) => Promise<T> ,
dishConnection: <T = DishConnection>(args: { offset?: Int | null, limit?: Int | null, where?: DishWhereInput | null, orderBy?: DishOrderByInput | null }, info?: GraphQLResolveInfo | string, options?: Options) => Promise<T> ,
dish: <T = Dish>(args: { where: DishWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise<T> ,
kitchenSinks: <T = Array<KitchenSink>>(args: { offset?: Int | null, limit?: Int | null, where?: KitchenSinkWhereInput | null, orderBy?: KitchenSinkOrderByInput | null }, info?: GraphQLResolveInfo | string, options?: Options) => Promise<T> ,
kitchenSink: <T = KitchenSink>(args: { where: KitchenSinkWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise<T>
Expand Down Expand Up @@ -372,6 +373,11 @@ export interface Dish extends BaseGraphQLObject {
kitchenSinkId: String
}

export interface DishConnection {
nodes: Array<Dish>
pageInfo: PageInfo
}

export interface KitchenSink extends BaseGraphQLObject {
id: ID_Output
createdAt: DateTime
Expand Down Expand Up @@ -403,6 +409,14 @@ export interface KitchenSink extends BaseGraphQLObject {
apiOnlyField?: String | null
}

export interface PageInfo {
limit: Float
offset: Float
totalCount: Float
hasNextPage: Boolean
hasPreviousPage: Boolean
}

export interface StandardDeleteResponse {
id: ID_Output
}
Expand Down
14 changes: 14 additions & 0 deletions src/test/generated/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ type Dish implements BaseGraphQLObject {
kitchenSinkId: String!
}

type DishConnection {
nodes: [Dish!]!
pageInfo: PageInfo!
}

input DishCreateInput {
name: String!
kitchenSinkId: ID!
Expand Down Expand Up @@ -374,8 +379,17 @@ type Mutation {
deleteKitchenSink(where: KitchenSinkWhereUniqueInput!): StandardDeleteResponse!
}

type PageInfo {
limit: Float!
offset: Float!
totalCount: Float!
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}

type Query {
dishes(offset: Int, limit: Int = 50, where: DishWhereInput, orderBy: DishOrderByInput): [Dish!]!
dishConnection(offset: Int, limit: Int = 50, where: DishWhereInput, orderBy: DishOrderByInput): DishConnection!
dish(where: DishWhereUniqueInput!): Dish!
kitchenSinks(offset: Int, limit: Int = 50, where: KitchenSinkWhereInput, orderBy: KitchenSinkOrderByInput): [KitchenSink!]!
kitchenSink(where: KitchenSinkWhereUniqueInput!): KitchenSink!
Expand Down
31 changes: 29 additions & 2 deletions src/test/modules/dish/dish.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@ import {
Mutation,
Query,
Resolver,
Root
Root,
ObjectType,
Field
} from 'type-graphql';
import { Inject } from 'typedi';

import { BaseContext, Fields, StandardDeleteResponse, UserId } from '../../../';
import {
BaseContext,
ConnectionResult,
Fields,
PageInfo,
StandardDeleteResponse,
UserId
} from '../../../';
import {
DishCreateInput,
DishCreateManyArgs,
Expand All @@ -25,6 +34,16 @@ import { KitchenSink } from '../kitchen-sink/kitchen-sink.model';

import { Dish } from './dish.model';
import { DishService } from './dish.service';
import { NestedFields } from '../../../decorators';

@ObjectType()
export class DishConnection implements ConnectionResult<Dish> {
@Field(() => [Dish], { nullable: false })
nodes!: Dish[];

@Field(() => PageInfo, { nullable: false })
pageInfo!: PageInfo;
}

@Resolver(Dish)
export class DishResolver {
Expand All @@ -45,6 +64,14 @@ export class DishResolver {
return this.service.find<DishWhereInput>(where, orderBy, limit, offset, fields);
}

@Authorized('dish:read')
@Query(() => DishConnection)
async dishConnection(
@Args() { where, orderBy, limit, offset }: DishWhereArgs
): Promise<DishConnection> {
return this.service.findConnection<DishWhereInput>(where, orderBy, limit, offset);
}

@Authorized('dish:read')
@Query(() => Dish)
async dish(@Arg('where') where: DishWhereUniqueInput): Promise<Dish> {
Expand Down
30 changes: 30 additions & 0 deletions src/tgql/PageInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Field, ObjectType } from 'type-graphql';

export interface ConnectionEdge<E> {
node: E;
cursor: string;
}

export interface ConnectionResult<E> {
nodes: E[]; // list of records returned from the database
edges?: ConnectionEdge<E>[];
pageInfo: PageInfo;
}

@ObjectType()
export class PageInfo {
@Field(() => Number, { nullable: false })
limit!: number;

@Field(() => Number, { nullable: false })
offset!: number;

@Field(() => Number, { nullable: false })
totalCount!: number;

@Field({ nullable: false })
hasNextPage!: boolean;

@Field({ nullable: false })
hasPreviousPage!: boolean;
}
1 change: 1 addition & 0 deletions src/tgql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { BaseModel } from '../core/BaseModel';
export * from './BaseResolver';
export * from './BaseWhereInput';
export * from './DeleteResponse';
export * from './PageInfo';
export * from './PaginationArgs';
export { StandardDeleteResponse } from './DeleteResponse';
export { loadFromGlobArray } from './loadGlobs';

0 comments on commit 11ae817

Please sign in to comment.