Skip to content

Commit

Permalink
refactor: move create connection type
Browse files Browse the repository at this point in the history
  • Loading branch information
calebmer committed Apr 16, 2016
1 parent c50e0b6 commit eb4d987
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 132 deletions.
65 changes: 65 additions & 0 deletions src/graphql/connection/createConnectionType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { memoize, camelCase, upperFirst } from 'lodash'
import createTableType from '../createTableType.js'
import { PageInfoType, CursorType } from '../connection/types.js'

import {
GraphQLObjectType,
GraphQLList,
GraphQLNonNull,
GraphQLInt,
} from 'graphql'

const pascalCase = string => upperFirst(camelCase(string))

const createTableConnectionType = memoize(table =>
new GraphQLObjectType({
name: pascalCase(`${table.name}_connection`),
description: `A connection to a list of \`${pascalCase(table.name)}\` items`,

// TODO: Implement a `ConnectionType` interface

fields: {
pageInfo: {
type: new GraphQLNonNull(PageInfoType),
description: `Information to aid in pagination of type \`${pascalCase(table.name)}\`.`,
resolve: pageInfo => pageInfo,
},
totalCount: {
type: GraphQLInt,
description: 'All of the items available to be queried in this connection.',
resolve: ({ totalCount }) => totalCount,
},
list: {
type: new GraphQLList(createTableType(table)),
description: `The queried list of \`${pascalCase(table.name)}\`.`,
resolve: ({ list }) => list,
},
edges: {
type: new GraphQLList(createTableEdgeType(table)),
description: 'A single item and a cursor to aid in pagination.',
resolve: ({ edges }) => edges,
},
},
})
)

export default createTableConnectionType

const createTableEdgeType = table =>
new GraphQLObjectType({
name: pascalCase(`${table.name}_edge`),
description: `An edge in the \`${pascalCase(`${table.name}_connection`)}\`.`,

fields: {
cursor: {
type: new GraphQLNonNull(CursorType),
description: 'The cursor describing the position of the edge’s associated node.',
resolve: ({ cursor }) => cursor || 'null',
},
node: {
type: createTableType(table),
description: 'The item at the end of the edge.',
resolve: ({ node }) => node,
},
},
})
47 changes: 47 additions & 0 deletions src/graphql/connection/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
Kind,
GraphQLScalarType,
GraphQLObjectType,
GraphQLNonNull,
GraphQLBoolean,
} from 'graphql'

const toBase64 = value => new Buffer(value.toString()).toString('base64')
const fromBase64 = value => new Buffer(value.toString(), 'base64').toString()

export const CursorType =
new GraphQLScalarType({
name: 'Cursor',
description: 'An opaque base64 encoded string describing a location in a list of items.',
serialize: toBase64,
parseValue: fromBase64,
parseLiteral: ast => (ast.kind === Kind.STRING ? fromBase64(ast.value) : null),
})

export const PageInfoType =
new GraphQLObjectType({
name: 'PageInfo',
description: 'Information about pagination in a connection.',
fields: {
hasNextPage: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'Are there items after our result set to be queried?',
resolve: ({ hasNextPage }) => hasNextPage,
},
hasPreviousPage: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'Are there items before our result set to be queried?',
resolve: ({ hasPreviousPage }) => hasPreviousPage,
},
startCursor: {
type: CursorType,
description: 'The cursor for the first item in the list.',
resolve: ({ startCursor }) => startCursor,
},
endCursor: {
type: CursorType,
description: 'The cursor for the last item in the list.',
resolve: ({ endCursor }) => endCursor,
},
},
})
100 changes: 3 additions & 97 deletions src/graphql/list/createTableListField.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { fromPairs, camelCase, snakeCase, upperFirst, toUpper } from 'lodash'
import createTableType from '../createTableType.js'
import { CursorType } from '../connection/types.js'
import createConnectionType from '../connection/createConnectionType.js'
import resolveTableList from './resolveTableList.js'

import {
Kind,
GraphQLScalarType,
GraphQLObjectType,
GraphQLEnumType,
GraphQLList,
GraphQLNonNull,
GraphQLInt,
GraphQLBoolean,
} from 'graphql'
Expand Down Expand Up @@ -82,7 +78,7 @@ const createTableListField = table => ({
// type will expect functions (that cache their values) and not traditional
// values. This improves performance when we don’t have to do potentially
// expensive queries on fields we don’t actually need.
type: createTableConnectionType(table),
type: createConnectionType(table),

resolve: resolveTableList(table),
})
Expand All @@ -106,93 +102,3 @@ const createTableOrderingEnum = table =>
.map(({ name }) => [toUpper(snakeCase(name)), { value: name }])
),
})

const createTableConnectionType = table =>
new GraphQLObjectType({
name: pascalCase(`${table.name}_connection`),
description: `A connection to a list of \`${pascalCase(table.name)}\` items`,

// TODO: Implement a `ConnectionType` interface

fields: {
pageInfo: {
type: new GraphQLNonNull(PageInfoType),
description: `Information to aid in pagination of type \`${pascalCase(table.name)}\`.`,
resolve: pageInfo => pageInfo,
},
totalCount: {
type: GraphQLInt,
description: 'All of the items available to be queried in this connection.',
resolve: ({ totalCount }) => totalCount,
},
list: {
type: new GraphQLList(createTableType(table)),
description: `The queried list of \`${pascalCase(table.name)}\`.`,
resolve: ({ list }) => list,
},
edges: {
type: new GraphQLList(createTableEdgeType(table)),
description: 'A single item and a cursor to aid in pagination.',
resolve: ({ edges }) => edges,
},
},
})

const createTableEdgeType = table =>
new GraphQLObjectType({
name: pascalCase(`${table.name}_edge`),
description: `An edge in the \`${pascalCase(`${table.name}_connection`)}\`.`,

fields: {
cursor: {
type: new GraphQLNonNull(CursorType),
description: 'The cursor describing the position of the edge’s associated node.',
resolve: ({ cursor }) => cursor || 'null',
},
node: {
type: createTableType(table),
description: 'The item at the end of the edge.',
resolve: ({ node }) => node,
},
},
})

const toBase64 = value => new Buffer(value.toString()).toString('base64')
const fromBase64 = value => new Buffer(value.toString(), 'base64').toString()

export const CursorType =
new GraphQLScalarType({
name: 'Cursor',
description: 'An opaque base64 encoded string describing a location in a list of items.',
serialize: toBase64,
parseValue: fromBase64,
parseLiteral: ast => (ast.kind === Kind.STRING ? fromBase64(ast.value) : null),
})

export const PageInfoType =
new GraphQLObjectType({
name: 'PageInfo',
description: 'Information about pagination in a connection.',
fields: {
hasNextPage: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'Are there items after our result set to be queried?',
resolve: ({ hasNextPage }) => hasNextPage,
},
hasPreviousPage: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'Are there items before our result set to be queried?',
resolve: ({ hasPreviousPage }) => hasPreviousPage,
},
startCursor: {
type: CursorType,
description: 'The cursor for the first item in the list.',
resolve: ({ startCursor }) => startCursor,
},
endCursor: {
type: CursorType,
description: 'The cursor for the last item in the list.',
resolve: ({ endCursor }) => endCursor,
},
},
})
40 changes: 40 additions & 0 deletions tests/graphql/connection/createConnectionType.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import expect from 'expect'
import { GraphQLNonNull, GraphQLList } from 'graphql'
import { TestTable } from '../../helpers.js'
import createConnectionType from '#/graphql/connection/createConnectionType.js'

describe('createConnectionType', () => {
it('will append “connection” to type name', () => {
const type = createConnectionType(new TestTable({ name: 'person' }))
expect(type.name).toEqual('PersonConnection')
})

it('will have a connection type with Relay connection fields', () => {
const type = createConnectionType(new TestTable({ name: 'person' }))
const fields = type.getFields()
expect(fields.pageInfo.type).toBeA(GraphQLNonNull)
expect(fields.pageInfo.type.ofType.name).toEqual('PageInfo')
expect(fields.totalCount.type.name).toEqual('Int')
expect(fields.edges.type).toBeA(GraphQLList)
expect(fields.edges.type.ofType.name).toEqual('PersonEdge')
})

it('will have a connection type with a plain list field', () => {
const type = createConnectionType(new TestTable({ name: 'person' }))
const fields = type.getFields()
expect(fields.list.type).toBeA(GraphQLList)
expect(fields.list.type.ofType.name).toEqual('Person')
})

it('will have edges with a node and a cursor', () => {
const type = createConnectionType(new TestTable({ name: 'person' }))
const edgeType = type.getFields().edges.type
expect(edgeType).toBeA(GraphQLList)
expect(edgeType.ofType.name).toEqual('PersonEdge')
expect(edgeType.ofType.getFields().cursor.type).toBeA(GraphQLNonNull)
expect(edgeType.ofType.getFields().cursor.type.ofType.name).toEqual('Cursor')
expect(edgeType.ofType.getFields().node.type.name).toEqual('Person')
})

it.skip('implements a connection interface')
})
36 changes: 1 addition & 35 deletions tests/graphql/list/createTableListField.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import expect from 'expect'
import { GraphQLObjectType, GraphQLNonNull, GraphQLList } from 'graphql'
import { GraphQLObjectType } from 'graphql'
import { TestTable } from '../../helpers.js'
import createTableListField from '#/graphql/list/createTableListField.js'

Expand All @@ -9,11 +9,6 @@ describe('createTableListField', () => {
expect(type).toBeA(GraphQLObjectType)
})

it('will append “connection” to type name', () => {
const { type } = createTableListField(new TestTable({ name: 'person' }))
expect(type.name).toEqual('PersonConnection')
})

it('will have Relay connection arguments', () => {
const field = createTableListField(new TestTable())
expect(field.args).toIncludeKeys(['first', 'last', 'before', 'after'])
Expand All @@ -29,33 +24,4 @@ describe('createTableListField', () => {
const field = createTableListField(new TestTable())
expect(field.args).toIncludeKeys(['orderBy', 'offset', 'descending'])
})

it('will have a connection type with Relay connection fields', () => {
const { type } = createTableListField(new TestTable({ name: 'person' }))
const fields = type.getFields()
expect(fields.pageInfo.type).toBeA(GraphQLNonNull)
expect(fields.pageInfo.type.ofType.name).toEqual('PageInfo')
expect(fields.totalCount.type.name).toEqual('Int')
expect(fields.edges.type).toBeA(GraphQLList)
expect(fields.edges.type.ofType.name).toEqual('PersonEdge')
})

it('will have a connection type with a plain list field', () => {
const { type } = createTableListField(new TestTable({ name: 'person' }))
const fields = type.getFields()
expect(fields.list.type).toBeA(GraphQLList)
expect(fields.list.type.ofType.name).toEqual('Person')
})

it('will have edges with a node and a cursor', () => {
const { type } = createTableListField(new TestTable({ name: 'person' }))
const edgeType = type.getFields().edges.type
expect(edgeType).toBeA(GraphQLList)
expect(edgeType.ofType.name).toEqual('PersonEdge')
expect(edgeType.ofType.getFields().cursor.type).toBeA(GraphQLNonNull)
expect(edgeType.ofType.getFields().cursor.type.ofType.name).toEqual('Cursor')
expect(edgeType.ofType.getFields().node.type.name).toEqual('Person')
})

it.skip('implements a connection interface')
})

0 comments on commit eb4d987

Please sign in to comment.