Skip to content

Commit d9df626

Browse files
feat: add ability to add fields to edges (related discussion #50)
* Add ability to add fields to edges * Add connectionResolver with edgeFields to tests
1 parent 6a1b101 commit d9df626

File tree

7 files changed

+258
-34
lines changed

7 files changed

+258
-34
lines changed

src/__mocks__/userTC.js

Lines changed: 124 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,26 @@ export const UserType = new GraphQLObjectType({
2929
},
3030
});
3131

32+
export const UserLinkType = new GraphQLObjectType({
33+
name: 'UserLink',
34+
fields: {
35+
id: {
36+
type: GraphQLInt,
37+
},
38+
type: {
39+
type: GraphQLString,
40+
},
41+
userId: {
42+
type: GraphQLInt,
43+
},
44+
otherUserId: {
45+
type: GraphQLInt,
46+
},
47+
},
48+
});
49+
3250
export const userTC = schemaComposer.createObjectTC(UserType);
51+
export const userLinkTC = schemaComposer.createObjectTC(UserLinkType);
3352

3453
export const userList = [
3554
{ id: 1, name: 'user01', age: 11, gender: 'm' },
@@ -49,6 +68,11 @@ export const userList = [
4968
{ id: 13, name: 'user13', age: 45, gender: 'f' },
5069
];
5170

71+
export const userLinkList = [
72+
{ id: 1, type: 'likes', userId: 1, otherUserId: 2 },
73+
{ id: 2, type: 'dislikes', userId: 2, otherUserId: 1 },
74+
];
75+
5276
const filterArgConfig = {
5377
name: 'filter',
5478
type: new GraphQLInputObjectType({
@@ -64,30 +88,71 @@ const filterArgConfig = {
6488
}),
6589
};
6690

67-
function filteredUserList(list, filter = {}) {
68-
let result = list.slice();
91+
const filterEdgeArgConfig = {
92+
name: 'filter',
93+
type: new GraphQLInputObjectType({
94+
name: 'FilterNodeEdgeUserInput',
95+
fields: {
96+
edge: {
97+
type: new GraphQLInputObjectType({
98+
name: 'FilterNodeEdgeEdgeUserInput',
99+
fields: {
100+
type: {
101+
type: GraphQLString,
102+
},
103+
},
104+
}),
105+
},
106+
node: {
107+
type: new GraphQLInputObjectType({
108+
name: 'FilterNodeNodeEdgeUserInput',
109+
fields: {
110+
gender: {
111+
type: GraphQLString,
112+
},
113+
age: {
114+
type: GraphQLInt,
115+
},
116+
},
117+
}),
118+
},
119+
},
120+
}),
121+
};
122+
123+
function filterUserLink(link, filter = {}) {
124+
let pred = true;
125+
if (filter.type) {
126+
pred = pred && link.type === filter.type;
127+
}
128+
return pred;
129+
}
130+
function filterUser(user, filter = {}) {
131+
let pred = true;
69132
if (filter.gender) {
70-
result = result.filter((o) => o.gender === filter.gender);
133+
pred = pred && user.gender === filter.gender;
71134
}
72135

73136
if (filter.id) {
74137
if (filter.id.$lt) {
75-
result = result.filter((o) => o.id < filter.id.$lt);
138+
pred = pred && user.id < filter.id.$lt;
76139
}
77140
if (filter.id.$gt) {
78-
result = result.filter((o) => o.id > filter.id.$gt);
141+
pred = pred && user.id > filter.id.$gt;
79142
}
80143
}
81144
if (filter.age) {
82145
if (filter.age.$lt) {
83-
result = result.filter((o) => o.age < filter.age.$lt);
146+
pred = pred && user.age < filter.age.$lt;
84147
}
85148
if (filter.age.$gt) {
86-
result = result.filter((o) => o.age > filter.age.$gt);
149+
pred = pred && user.age > filter.age.$gt;
87150
}
88151
}
89-
90-
return result;
152+
return pred;
153+
}
154+
function filteredUserList(list, filter = {}) {
155+
return list.slice().filter((o) => filterUser(o, filter));
91156
}
92157

93158
function sortUserList(list, sortValue = {}) {
@@ -171,6 +236,56 @@ export const countResolver = schemaComposer.createResolver({
171236
});
172237
userTC.setResolver('count', countResolver);
173238

239+
function getThroughLinkResolver(list, filter) {
240+
const nodeFilter = filter ? filter.node : {};
241+
const edgeFilter = filter ? filter.edge : {};
242+
return list
243+
.map((link) => ({
244+
...link,
245+
node: userList.find((u) => u.id === link.otherUserId && filterUser(u, nodeFilter)),
246+
}))
247+
.filter((l) => !!l.node && filterUserLink(l, edgeFilter));
248+
}
249+
export const findManyThroughLinkResolver = schemaComposer.createResolver({
250+
name: 'findManyThroughLink',
251+
kind: 'query',
252+
type: UserType,
253+
args: {
254+
filter: filterEdgeArgConfig,
255+
limit: GraphQLInt,
256+
skip: GraphQLInt,
257+
},
258+
resolve: async (resolveParams) => {
259+
const args = resolveParams.args || {};
260+
const { limit, skip } = args;
261+
262+
let list = userLinkList.slice();
263+
264+
if (skip) {
265+
list = list.slice(skip);
266+
}
267+
268+
if (limit) {
269+
list = list.slice(0, limit);
270+
}
271+
return getThroughLinkResolver(list, args.filter);
272+
},
273+
});
274+
userTC.setResolver('findManyThroughLink', findManyThroughLinkResolver);
275+
export const countThroughLinkResolver = schemaComposer.createResolver({
276+
name: 'count',
277+
kind: 'query',
278+
type: GraphQLInt,
279+
args: {
280+
filter: filterEdgeArgConfig,
281+
},
282+
resolve: async (resolveParams) => {
283+
const args = resolveParams.args || {};
284+
return getThroughLinkResolver(userLinkList.slice(), args.filter).length;
285+
},
286+
});
287+
userTC.setResolver('countThroughLink', countThroughLinkResolver);
288+
174289
export const sortOptions: ConnectionSortMapOpts = {
175290
ID_ASC: {
176291
value: { id: 1 },

src/__tests__/__snapshots__/connectionResolver-test.js.snap

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,73 @@ exports[`connectionResolver "Relay Cursor Connections Specification (Pagination
44

55
exports[`connectionResolver "Relay Cursor Connections Specification (Pagination algorithm)": ApplyCursorsToEdges(allEdges, before, after): should throw error if \`last\` is less than 0 1`] = `[Error: Argument \`last\` should be non-negative number.]`;
66

7+
exports[`connectionResolver edges with data correctly handles filtering 1`] = `
8+
Object {
9+
"count": 1,
10+
"edges": Array [
11+
Object {
12+
"cursor": "MA==",
13+
"id": 1,
14+
"node": Object {
15+
"age": 12,
16+
"gender": "m",
17+
"id": 2,
18+
"name": "user02",
19+
},
20+
"otherUserId": 2,
21+
"type": "likes",
22+
"userId": 1,
23+
},
24+
],
25+
"pageInfo": Object {
26+
"endCursor": "MA==",
27+
"hasNextPage": false,
28+
"hasPreviousPage": false,
29+
"startCursor": "MA==",
30+
},
31+
}
32+
`;
33+
34+
exports[`connectionResolver edges with data correctly resolves with edges 1`] = `
35+
Object {
36+
"count": 2,
37+
"edges": Array [
38+
Object {
39+
"cursor": "MA==",
40+
"id": 1,
41+
"node": Object {
42+
"age": 12,
43+
"gender": "m",
44+
"id": 2,
45+
"name": "user02",
46+
},
47+
"otherUserId": 2,
48+
"type": "likes",
49+
"userId": 1,
50+
},
51+
Object {
52+
"cursor": "MQ==",
53+
"id": 2,
54+
"node": Object {
55+
"age": 11,
56+
"gender": "m",
57+
"id": 1,
58+
"name": "user01",
59+
},
60+
"otherUserId": 1,
61+
"type": "dislikes",
62+
"userId": 2,
63+
},
64+
],
65+
"pageInfo": Object {
66+
"endCursor": "MQ==",
67+
"hasNextPage": false,
68+
"hasPreviousPage": false,
69+
"startCursor": "MA==",
70+
},
71+
}
72+
`;
73+
774
exports[`connectionResolver fallback logic (offset in cursor) should throw error if \`first\` is less than 0 1`] = `[Error: Argument \`first\` should be non-negative number.]`;
875

976
exports[`connectionResolver fallback logic (offset in cursor) should throw error if \`last\` is less than 0 1`] = `[Error: Argument \`last\` should be non-negative number.]`;

src/__tests__/connectionResolver-test.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import { Resolver } from 'graphql-compose';
55
import { GraphQLInt, GraphQLString } from 'graphql-compose/lib/graphql';
6-
import { userTC, userList, sortOptions } from '../__mocks__/userTC';
6+
import { userTC, userLinkTC, userList, sortOptions } from '../__mocks__/userTC';
77
import { dataToCursor } from '../cursor';
88
import { prepareConnectionResolver, prepareRawQuery, preparePageInfo } from '../connectionResolver';
99

@@ -829,4 +829,30 @@ describe('connectionResolver', () => {
829829
expect(data.pageInfo.hasPreviousPage).toBe(true);
830830
});
831831
});
832+
833+
describe('edges with data', () => {
834+
const edgeDataResolver = prepareConnectionResolver(userTC, {
835+
countResolverName: 'countThroughLink',
836+
findResolverName: 'findManyThroughLink',
837+
sort: sortOptions,
838+
defaultLimit: 5,
839+
edgeFields: userLinkTC.getFields(),
840+
});
841+
it('correctly resolves with edges', async () => {
842+
const data = await edgeDataResolver.resolve({
843+
args: {},
844+
projection: { count: true, edges: true },
845+
});
846+
expect(data.edges.length).toBe(2);
847+
expect(data).toMatchSnapshot();
848+
});
849+
it('correctly handles filtering', async () => {
850+
const data = await edgeDataResolver.resolve({
851+
args: { filter: { edge: { type: 'likes' } } },
852+
projection: { count: true, edges: true },
853+
});
854+
expect(data.edges.length).toBe(1);
855+
expect(data).toMatchSnapshot();
856+
});
857+
});
832858
});

src/composeWithConnection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { ComposeWithConnectionOpts } from './connectionResolver';
66

77
export function composeWithConnection<TSource, TContext>(
88
typeComposer: ObjectTypeComposer<TSource, TContext>,
9-
opts: ComposeWithConnectionOpts
9+
opts: ComposeWithConnectionOpts<TContext>
1010
): ObjectTypeComposer<TSource, TContext> {
1111
if (!(typeComposer instanceof ObjectTypeComposer)) {
1212
throw new Error('You should provide ObjectTypeComposer instance to composeWithRelay method');

0 commit comments

Comments
 (0)