Skip to content

Commit 59476e2

Browse files
committed
Porting connections from graphql-mongoose
1 parent e17faea commit 59476e2

File tree

5 files changed

+227
-0
lines changed

5 files changed

+227
-0
lines changed

src/composeWithConnection.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* @flow */
2+
/* eslint-disable no-use-before-define */
3+
4+
import { TypeComposer } from 'graphql-compose';
5+
6+
7+
export function composeWithConnection(
8+
typeComposer: TypeComposer
9+
): TypeComposer {
10+
if (!(typeComposer instanceof TypeComposer)) {
11+
throw new Error('You should provide TypeComposer instance to composeWithRelay method');
12+
}
13+
14+
const findById = typeComposer.getResolver('findById');
15+
if (!findById) {
16+
throw new Error(`TypeComposer(${typeComposer.getTypeName()}) provided to composeWithRelay `
17+
+ 'should have findById resolver.');
18+
}
19+
20+
return typeComposer;
21+
}

src/connectionResolver.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { cursorToId, idToCursor, dotObject } from '../utils';
2+
import { projectionCollection, projectionFixRenamed } from './projection';
3+
import {
4+
getArgsFromOpts,
5+
getRenamedMongooseFields,
6+
} from './commons';
7+
import { connectionArgs } from 'graphql-relay';
8+
9+
10+
/**
11+
* Helper to get an empty connection.
12+
*/
13+
export function emptyConnection() {
14+
return {
15+
count: 0,
16+
edges: [],
17+
pageInfo: {
18+
startCursor: null,
19+
endCursor: null,
20+
hasPreviousPage: false,
21+
hasNextPage: false,
22+
},
23+
};
24+
}
25+
26+
27+
export function getIdFromCursor(cursor) {
28+
if (cursor === undefined || cursor === null) {
29+
return null;
30+
}
31+
32+
return cursorToId(cursor);
33+
}
34+
35+
36+
export function getByConnectionResolver(typeName, resolverOpts = {}) {
37+
const mongooseModel = Storage.MongooseModels.get(typeName);
38+
39+
if (!mongooseModel) {
40+
if (process.env.NODE_ENV !== 'production') {
41+
console.log(`graphql-mongoose warn: could not find model '${typeName}' `
42+
+ `for getByConnectionResolver. You should call populateModels(${typeName}).`);
43+
}
44+
}
45+
46+
const args = {
47+
...connectionArgs,
48+
...getArgsFromOpts(typeName, resolverOpts),
49+
};
50+
51+
const renamedFields = getRenamedMongooseFields(mongooseModel);
52+
const projectionFn = renamedFields
53+
? projectionFixRenamed.bind(this, renamedFields)
54+
: projectionCollection;
55+
56+
const resolve = async function(root, queryArgs = {}, context, info) {
57+
const { before, after, first, last, sort = { _id: 1 }, filter } = queryArgs;
58+
59+
const begin = getIdFromCursor(after);
60+
const end = getIdFromCursor(before);
61+
62+
const skip = (first - last) || 0;
63+
const limit = last || first;
64+
65+
const selector = Object.assign({}, filter ? dotObject(filter) : null);
66+
if (begin) {
67+
selector._id = {};
68+
selector._id.$gt = begin;
69+
}
70+
71+
if (end) {
72+
selector._id = {};
73+
selector._id.$lt = end;
74+
}
75+
76+
if (mongooseModel) {
77+
const projFields = projectionFn(info);
78+
79+
let count = 0;
80+
if (projFields.count) {
81+
count = await mongoose.getCount(mongooseModel, selector);
82+
}
83+
84+
const result = await mongoose.getList(
85+
mongooseModel,
86+
selector,
87+
{ limit, skip, sort },
88+
projFields
89+
);
90+
91+
if (result.length === 0) {
92+
return emptyConnection();
93+
}
94+
95+
const edges = result.map((value) => ({
96+
cursor: idToCursor(value._id),
97+
node: value,
98+
}));
99+
100+
return {
101+
count,
102+
edges,
103+
pageInfo: {
104+
startCursor: edges[0].cursor,
105+
endCursor: edges[edges.length - 1].cursor,
106+
hasPreviousPage: skip !== 0 || !!begin,
107+
hasNextPage: result.length === limit,
108+
},
109+
};
110+
}
111+
112+
return null;
113+
};
114+
115+
return { args, resolve };
116+
}

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { composeWithConnection } from './composeWithConnection';
2+
3+
export default composeWithConnection;

src/type/connection.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* eslint-disable arrow-body-style */
2+
3+
import {
4+
GraphQLInt,
5+
GraphQLString,
6+
GraphQLObjectType,
7+
GraphQLNonNull,
8+
GraphQLList,
9+
} from 'graphql';
10+
11+
import PageInfoType from './pageInfo';
12+
13+
export function getEdgeType(graphqlType, opts) {
14+
const name = `${graphqlType.name}Edge`;
15+
16+
return new GraphQLObjectType({
17+
name,
18+
description: 'An edge in a connection.',
19+
fields: () => ({
20+
node: {
21+
type: graphqlType,
22+
description: 'The item at the end of the edge',
23+
},
24+
cursor: {
25+
type: new GraphQLNonNull(GraphQLString),
26+
description: 'A cursor for use in pagination',
27+
},
28+
}),
29+
});
30+
}
31+
32+
33+
export function getConnectionType(graphqlType, opts) {
34+
const name = `${graphqlType.name}Connection`;
35+
36+
return Storage.getTypeWithCache(name, () => {
37+
return new GraphQLObjectType({
38+
name,
39+
description: 'A connection to a list of items.',
40+
fields: () => ({
41+
count: {
42+
type: GraphQLInt,
43+
description: 'Total object count.',
44+
},
45+
pageInfo: {
46+
type: new GraphQLNonNull(PageInfoType),
47+
description: 'Information to aid in pagination.',
48+
},
49+
edges: {
50+
type: new GraphQLList(getEdgeType(graphqlType, opts)),
51+
description: 'Information to aid in pagination.',
52+
},
53+
}),
54+
});
55+
});
56+
}

src/type/pageInfo.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
GraphQLBoolean,
3+
GraphQLString,
4+
GraphQLObjectType,
5+
GraphQLNonNull,
6+
} from 'graphql';
7+
8+
const PageInfoType = new GraphQLObjectType({
9+
name: 'PageInfo',
10+
description: 'Information about pagination in a connection.',
11+
fields: () => ({
12+
hasNextPage: {
13+
type: new GraphQLNonNull(GraphQLBoolean),
14+
description: 'When paginating forwards, are there more items?',
15+
},
16+
hasPreviousPage: {
17+
type: new GraphQLNonNull(GraphQLBoolean),
18+
description: 'When paginating backwards, are there more items?',
19+
},
20+
startCursor: {
21+
type: GraphQLString,
22+
description: 'When paginating backwards, the cursor to continue.',
23+
},
24+
endCursor: {
25+
type: GraphQLString,
26+
description: 'When paginating forwards, the cursor to continue.',
27+
},
28+
}),
29+
});
30+
31+
export default PageInfoType;

0 commit comments

Comments
 (0)