Skip to content

Commit

Permalink
feat: Add option to specify connectionResolverName
Browse files Browse the repository at this point in the history
  • Loading branch information
Scimonster authored and nodkz committed Feb 26, 2019
1 parent d00e53f commit 926ceb1
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import composeWithConnection from 'graphql-compose-connection';
import userTypeComposer from './user.js';

composeWithConnection(userTypeComposer, {
connectionResolverName: 'connection', // Default
findResolverName: 'findMany',
countResolverName: 'count',
sort: {
Expand Down
47 changes: 47 additions & 0 deletions src/__tests__/composeWithConnection-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,53 @@ describe('composeWithRelay', () => {
expect(myTC.getResolver('connection')).toBeTruthy();
expect(myTC.getResolver('connection').resolve()).toBe('mockData');
});

it('should add resolver with user-specified name', () => {
let myTC = TypeComposer.create('type CustomComplex { a: String, b: Int }');
myTC.addResolver({
name: 'count',
resolve: () => 1,
});
myTC.addResolver({
name: 'findMany',
resolve: () => ['mockData'],
});
myTC = composeWithConnection(myTC, {
connectionResolverName: 'customConnection',
countResolverName: 'count',
findResolverName: 'findMany',
sort: sortOptions,
});

expect(myTC.getResolver('customConnection')).toBeTruthy();
expect(myTC.hasResolver('connection')).toBeFalsy();
});

it('should add two connection resolvers', () => {
let myTC = TypeComposer.create('type CustomComplex { a: String, b: Int }');
myTC.addResolver({
name: 'count',
resolve: () => 1,
});
myTC.addResolver({
name: 'findMany',
resolve: () => ['mockData'],
});
myTC = composeWithConnection(myTC, {
countResolverName: 'count',
findResolverName: 'findMany',
sort: sortOptions,
});
myTC = composeWithConnection(myTC, {
connectionResolverName: 'customConnection',
countResolverName: 'count',
findResolverName: 'findMany',
sort: sortOptions,
});

expect(myTC.hasResolver('connection')).toBeTruthy();
expect(myTC.getResolver('customConnection')).toBeTruthy();
});
});

describe('check `connection` resolver props', () => {
Expand Down
5 changes: 3 additions & 2 deletions src/composeWithConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { TypeComposer } from 'graphql-compose';
import { prepareConnectionResolver } from './connectionResolver';
import type { ComposeWithConnectionOpts } from './connectionResolver';
import { resolverName } from './utils/name';

export function composeWithConnection(
typeComposer: TypeComposer,
Expand All @@ -16,12 +17,12 @@ export function composeWithConnection(
throw new Error('You should provide non-empty options to composeWithConnection');
}

if (typeComposer.hasResolver('connection')) {
if (typeComposer.hasResolver(resolverName(opts.connectionResolverName))) {
return typeComposer;
}

const resolver = prepareConnectionResolver(typeComposer, opts);

typeComposer.setResolver('connection', resolver);
typeComposer.setResolver(resolverName(opts.connectionResolverName), resolver);
return typeComposer;
}
6 changes: 4 additions & 2 deletions src/connectionResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import type { GraphQLResolveInfo } from 'graphql-compose/lib/graphql';
import { prepareConnectionType } from './types/connectionType';
import { prepareSortType } from './types/sortInputType';
import { cursorToData, dataToCursor, type CursorDataType } from './cursor';
import { resolverName } from './utils/name';

export type ComposeWithConnectionOpts = {
connectionResolverName?: string,
findResolverName: string,
countResolverName: string,
sort: ConnectionSortMapOpts,
Expand Down Expand Up @@ -125,8 +127,8 @@ export function prepareConnectionResolver(
const sortEnumType = prepareSortType(tc, opts);

return new tc.constructor.schemaComposer.Resolver({
type: prepareConnectionType(tc),
name: 'connection',
type: prepareConnectionType(tc, opts.connectionResolverName),
name: resolverName(opts.connectionResolverName),
kind: 'query',
args: {
first: {
Expand Down
21 changes: 21 additions & 0 deletions src/types/__tests__/connectionType-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,31 @@ describe('types/connectionType.js', () => {
expect(prepareConnectionType(userTypeComposer)).toBeInstanceOf(GraphQLObjectType);
});

it('should return the same GraphQLObjectType object when called again', () => {
const firstConnectionType = prepareConnectionType(userTypeComposer);
const secondConnectionType = prepareConnectionType(userTypeComposer);
expect(firstConnectionType).toBeInstanceOf(GraphQLObjectType);
expect(firstConnectionType).toBe(secondConnectionType);
});

it('should return a separate GraphQLObjectType with a different name', () => {
const connectionType = prepareConnectionType(userTypeComposer);
const otherConnectionType = prepareConnectionType(userTypeComposer, 'otherConnection');
expect(connectionType).toBeInstanceOf(GraphQLObjectType);
expect(otherConnectionType).toBeInstanceOf(GraphQLObjectType);
expect(connectionType).not.toBe(otherConnectionType);
});

it('should have name ending with `Connection`', () => {
expect(prepareConnectionType(userTypeComposer).name).toBe('UserConnection');
});

it('should have name ending with `OtherConnection` when passed lowercase otherConnection', () => {
expect(prepareConnectionType(userTypeComposer, 'otherConnection').name).toBe(
'UserOtherConnection'
);
});

it('should have field `count` with provided Type', () => {
const tc = new TypeComposer(prepareConnectionType(userTypeComposer));
const countType: any = tc.getFieldType('count');
Expand Down
17 changes: 17 additions & 0 deletions src/types/__tests__/sortInputType-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,23 @@ describe('types/sortInputType.js', () => {
expect(sortType.name).toBe('SortConnectionUserEnum');
});

it('should have name `Sort[resolverName][typeName]Enum`', () => {
const otherSortType = prepareSortType(userTypeComposer, {
sort: {
_ID_ASC: {
value: { id: 1 },
cursorFields: ['id'],
beforeCursorQuery: () => {},
afterCursorQuery: () => {},
},
},
findResolverName: 'finMany',
countResolverName: 'count',
connectionResolverName: 'otherConnection',
});
expect(otherSortType.name).toBe('SortOtherConnectionUserEnum');
});

it('should have enum values', () => {
const etc = EnumTypeComposer.create(sortType);
expect(etc.hasField('_ID_ASC')).toBeTruthy();
Expand Down
18 changes: 13 additions & 5 deletions src/types/connectionType.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import type { TypeComposer } from 'graphql-compose';

import PageInfoType from './pageInfoType';
import { typeName } from '../utils/name';

const cachedConnectionTypes = new WeakMap();
const cachedEdgeTypes = new WeakMap();
Expand Down Expand Up @@ -47,12 +48,15 @@ export function prepareEdgeType(typeComposer: TypeComposer): GraphQLObjectType {
return edgeType;
}

export function prepareConnectionType(typeComposer: TypeComposer): GraphQLObjectType {
const name = `${typeComposer.getTypeName()}Connection`;
export function prepareConnectionType(
typeComposer: TypeComposer,
resolverName: ?string
): GraphQLObjectType {
const name = `${typeComposer.getTypeName()}${typeName(resolverName)}`;
const type = typeComposer.getType();

if (cachedConnectionTypes.has(type)) {
return (cachedConnectionTypes.get(type): any);
if (cachedConnectionTypes.has(type) && (cachedConnectionTypes.get(type): any).has(name)) {
return ((cachedConnectionTypes.get(type): any).get(name): any);
}

const connectionType = new GraphQLObjectType({
Expand Down Expand Up @@ -81,6 +85,10 @@ export function prepareConnectionType(typeComposer: TypeComposer): GraphQLObject
// $FlowFixMe
connectionType.ofType = type;

cachedConnectionTypes.set(type, connectionType);
if (!cachedConnectionTypes.has(type)) {
cachedConnectionTypes.set(type, new Map());
// Can't use WeakMap because keys must be strings
}
(cachedConnectionTypes.get(type): any).set(name, connectionType);
return connectionType;
}
5 changes: 4 additions & 1 deletion src/types/sortInputType.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { TypeComposer } from 'graphql-compose';
import { GraphQLEnumType } from 'graphql-compose/lib/graphql';
import { isFunction } from '../utils/is';
import { typeName as uppercaseTypeName } from '../utils/name';
import type { ConnectionSortOpts, ComposeWithConnectionOpts } from '../connectionResolver';

export function prepareSortType(
Expand All @@ -14,7 +15,9 @@ export function prepareSortType(
throw new Error('Option `sort` should not be empty in composeWithConnection');
}

const typeName = `SortConnection${typeComposer.getTypeName()}Enum`;
const typeName = `Sort${uppercaseTypeName(
opts.connectionResolverName
)}${typeComposer.getTypeName()}Enum`;

const sortKeys = Object.keys(opts.sort);
if (sortKeys.length === 0) {
Expand Down
10 changes: 10 additions & 0 deletions src/utils/name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* @flow */

export function resolverName(name: ?string) {
return name || 'connection';
}

export function typeName(name: ?string) {
const lowercaseName = resolverName(name);
return lowercaseName[0].toUpperCase() + lowercaseName.slice(1);
}

0 comments on commit 926ceb1

Please sign in to comment.