Skip to content

Commit

Permalink
Merge pull request #3 from goldcaddy77/find-and-count-connection
Browse files Browse the repository at this point in the history
Find and count connection
  • Loading branch information
ikoenigsknecht authored Apr 7, 2020
2 parents fad23f8 + 0a0ac57 commit 8728725
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 67 deletions.
31 changes: 22 additions & 9 deletions src/core/BaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
import { StandardDeleteResponse } from '../tgql';
import { addQueryBuilderWhereItem } from '../torm';

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

interface BaseOptions {
manager?: EntityManager; // Allows consumers to pass in a TransactionManager
Expand Down Expand Up @@ -51,29 +51,42 @@ 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[]> {
const qb = this.buildFindQuery<W>(where, orderBy, limit, offset, fields);
return qb.getMany();
return this.buildFindQuery<W>(where, orderBy, limit, offset, fields).getMany();
}

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

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

Expand Down
5 changes: 0 additions & 5 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ export interface WhereInput {
id_in?: IDType[];
}

export interface FindAndCountResult<E> {
records: E[]; // list of records returned from the database
total: number; // total number of records found
}

export interface DeleteReponse {
id: IDType;
}
Expand Down
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
18 changes: 12 additions & 6 deletions src/test/functional/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ describe('server', () => {
});

test('queries for dishes with pagination', async () => {
expect.assertions(4);
const { dishes, pageInfo } = await binding.query.dishesPaginated(
expect.assertions(6);
const { nodes, pageInfo } = await binding.query.dishConnection(
{ offset: 0, orderBy: 'createdAt_ASC', limit: 1 },
`{
dishes {
nodes {
name
kitchenSink {
emailField
Expand All @@ -137,15 +137,21 @@ describe('server', () => {
pageInfo {
limit
offset
total
totalCount
hasNextPage
hasPreviousPage
}
}`
);

expect(dishes).toMatchSnapshot();
console.log('test', nodes, pageInfo);

expect(nodes).toMatchSnapshot();
expect(pageInfo.offset).toEqual(0);
expect(pageInfo.limit).toEqual(1);
expect(pageInfo.total).toEqual(20);
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 => {
Expand Down
10 changes: 6 additions & 4 deletions src/test/generated/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +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> ,
dishesPaginated: <T = DishesPaginated>(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 @@ -373,8 +373,8 @@ export interface Dish extends BaseGraphQLObject {
kitchenSinkId: String
}

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

Expand Down Expand Up @@ -412,7 +412,9 @@ export interface KitchenSink extends BaseGraphQLObject {
export interface PageInfo {
limit: Float
offset: Float
total: Float
totalCount: Float
hasNextPage: Boolean
hasPreviousPage: Boolean
}

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

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

input DishCreateInput {
name: String!
kitchenSinkId: ID!
}

type DishesPaginated {
dishes: [Dish!]!
pageInfo: PageInfo!
}

enum DishOrderByInput {
createdAt_ASC
createdAt_DESC
Expand Down Expand Up @@ -382,12 +382,14 @@ type Mutation {
type PageInfo {
limit: Float!
offset: Float!
total: Float!
totalCount: Float!
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}

type Query {
dishes(offset: Int, limit: Int = 50, where: DishWhereInput, orderBy: DishOrderByInput): [Dish!]!
dishesPaginated(offset: Int, limit: Int = 50, where: DishWhereInput, orderBy: DishOrderByInput): DishesPaginated!
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
51 changes: 15 additions & 36 deletions src/test/modules/dish/dish.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ import {
} 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 @@ -30,21 +37,9 @@ import { DishService } from './dish.service';
import { NestedFields } from '../../../decorators';

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

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

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

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

@Field(() => PageInfo, { nullable: false })
pageInfo!: PageInfo;
Expand All @@ -70,27 +65,11 @@ export class DishResolver {
}

@Authorized('dish:read')
@Query(() => DishesPaginated)
async dishesPaginated(
@Args() { where, orderBy, limit, offset }: DishWhereArgs,
@NestedFields() fields: any
): Promise<DishesPaginated> {
const { records: dishes, total } = await this.service.findAndCount<DishWhereInput>(
where,
orderBy,
limit,
offset,
fields.dishes || []
);

return {
dishes,
pageInfo: {
limit: limit == null ? total : limit,
offset: offset == null ? 0 : offset,
total
}
};
@Query(() => DishConnection)
async dishConnection(
@Args() { where, orderBy, limit, offset }: DishWhereArgs
): Promise<DishConnection> {
return this.service.findConnection<DishWhereInput>(where, orderBy, limit, offset);
}

@Authorized('dish:read')
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 8728725

Please sign in to comment.