Skip to content

Commit 632856d

Browse files
committed
Fix connection resolver due spec
To determine what edges to return, the connection evaluates the before and after cursors to filter the edges, then evaluates first to slice the edges, then last to slice the edges. https://facebook.github.io/relay/graphql/connections.htm#sec-Pagination-algorithm
1 parent 995ec07 commit 632856d

File tree

4 files changed

+67
-63
lines changed

4 files changed

+67
-63
lines changed

src/composeWithConnection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function composeWithConnection(
2424

2525
const resolver = prepareConnectionResolver(
2626
typeComposer,
27-
opts,
27+
opts
2828
);
2929

3030
typeComposer.addResolver(resolver);

src/definition.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ import type {
55
ResolveParams as _ResolveParams,
66
} from 'graphql-compose/lib/definition.js';
77

8+
export type composeWithConnectionOpts = {
9+
findResolverName: string,
10+
countResolverName: string,
11+
sort: connectionSortMapOpts,
12+
};
13+
14+
export type connectionSortMapOpts = {
15+
[sortName: string]: connectionSortOpts,
16+
};
17+
18+
export type connectionSortOpts = {
19+
uniqueFields: string[],
20+
sortValue: mixed,
21+
directionFilter: (<T>(cursorData: CursorDataType, filterArg: T, isBefore: boolean) => T),
22+
};
23+
824
export type ResolveParams = _ResolveParams;
925

1026
export type ConnectionResolveParams = {
@@ -27,22 +43,6 @@ export type CursorDataType = {
2743
[fieldName: string]: mixed,
2844
};
2945

30-
export type connectionSortOpts = {
31-
uniqueFields: string[],
32-
sortValue: mixed,
33-
cursorToFilter: (<T>(cursorData: CursorDataType, filterArg: T) => T),
34-
};
35-
36-
export type connectionSortMapOpts = {
37-
[sortName: string]: connectionSortOpts,
38-
};
39-
40-
export type composeWithConnectionOpts = {
41-
findResolverName: string,
42-
countResolverName: string,
43-
sort: connectionSortMapOpts,
44-
};
45-
4646
export type GraphQLConnectionType = {
4747
count: number,
4848
edges: [GraphQLConnectionEdgeType],

src/resolvers/connectionResolver.js

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,12 @@ export function prepareConnectionResolver(
7575
...additionalArgs,
7676
sort: {
7777
type: sortEnumType,
78-
defaultValue: sortEnumType.getValues()[0].name, // first enum used by default
78+
defaultValue: sortEnumType.getValues()[0].value, // first enum used by default
7979
description: 'Sort argument for data ordering',
8080
},
8181
},
82-
resolve: (resolveParams: ConnectionResolveParams) => {
82+
resolve: async (resolveParams: ConnectionResolveParams) => {
83+
let countPromise;
8384
const { projection = {}, args = {} } = resolveParams;
8485
const findManyParams: ResolveParams = Object.assign(
8586
{},
@@ -88,39 +89,51 @@ export function prepareConnectionResolver(
8889
);
8990
const sortOptions: connectionSortOpts = args.sort;
9091

91-
const first = parseInt(args.first, 10);
92+
let filter = resolveParams.args.filter || {};
93+
const beginCursorData = cursorToData(args.after);
94+
if (beginCursorData) {
95+
filter = sortOptions.directionFilter(beginCursorData, filter, false);
96+
}
97+
const endCursorData = cursorToData(args.before);
98+
if (endCursorData) {
99+
filter = sortOptions.directionFilter(endCursorData, filter, true);
100+
}
101+
findManyParams.args.filter = filter;
102+
103+
104+
let first = parseInt(args.first, 10);
92105
const last = parseInt(args.last, 10);
93106

94-
const limit = last || first;
107+
if (projection.count) {
108+
countPromise = countResolve(findManyParams);
109+
} else if (!first && last) {
110+
countPromise = countResolve(findManyParams);
111+
} else {
112+
countPromise = Promise.resolve(0);
113+
}
114+
115+
if (!first && last) {
116+
first = (await countPromise) || 0;
117+
}
118+
119+
const limit = first;
95120
const skip = (first - last) || 0;
96121

97122
findManyParams.args.limit = limit + 1; // +1 document, to check next page presence
98123
if (skip > 0) {
99124
findManyParams.args.skip = skip;
100125
}
101126

102-
let filter = findManyParams.args.filter || {};
103-
const beginCursorData = cursorToData(args.after);
104-
if (beginCursorData) {
105-
filter = sortOptions.cursorToFilter(beginCursorData, filter);
106-
}
107-
const endCursorData = cursorToData(args.before);
108-
if (endCursorData) {
109-
filter = sortOptions.cursorToFilter(endCursorData, filter);
110-
}
111-
findManyParams.args.filter = filter;
112-
findManyParams.args.skip = skip;
113-
114127
findManyParams.args.sort = sortOptions.sortValue;
115128
findManyParams.projection = projection;
116129
sortOptions.uniqueFields.forEach(fieldName => {
117130
findManyParams.projection[fieldName] = true;
118131
});
119132

120-
let countPromise;
121-
if (projection.count) {
122-
countPromise = countResolve(resolveParams);
123-
}
133+
findManyParams.projection['count'] = true;
134+
findManyParams.projection['age'] = true;
135+
findManyParams.projection['name'] = true;
136+
124137
const hasPreviousPage = skip > 0;
125138
let hasNextPage = false; // will be requested +1 document, to check next page presence
126139

@@ -132,13 +145,15 @@ export function prepareConnectionResolver(
132145
return result;
133146
};
134147

135-
return findManyResolve(findManyParams)
136-
.then(recordList => {
148+
console.log(findManyParams.args);
149+
150+
return Promise.all([findManyResolve(findManyParams), countPromise])
151+
.then(([recordList, count]) => {
137152
const edges = [];
138153
// if returned more than `limit` records, strip array and mark that exists next page
139154
if (recordList.length > limit) {
140155
hasNextPage = true;
141-
recordList = recordList.slice(0, limit - 1);
156+
recordList = recordList.slice(0, limit);
142157
}
143158
// transform record to object { cursor, node }
144159
recordList.forEach(record => {
@@ -147,18 +162,12 @@ export function prepareConnectionResolver(
147162
node: record,
148163
});
149164
});
150-
return edges;
165+
return [edges, count];
151166
})
152-
.then(async (edges) => {
167+
.then(([edges, count]) => {
153168
const result = emptyConnection();
154-
155-
// pass `edge` data
156169
result.edges = edges;
157-
158-
// if exists countPromise, await it's data
159-
if (countPromise) {
160-
result.count = await countPromise;
161-
}
170+
result.count = count;
162171

163172
// pageInfo may be extended, so set data gradually
164173
if (edges.length > 0) {
@@ -187,17 +196,17 @@ export function emptyConnection(): GraphQLConnectionType {
187196
};
188197
}
189198

190-
export function cursorToData(id?: ?string): ?CursorDataType {
191-
if (id) {
199+
export function cursorToData(cursor?: ?string): ?CursorDataType {
200+
if (cursor) {
192201
try {
193-
return JSON.parse(id) || null;
202+
return JSON.parse(cursor) || null;
194203
} catch (err) {
195204
return null;
196205
}
197206
}
198207
return null;
199208
}
200209

201-
export function dataToCursor(cursorData: CursorDataType): string {
202-
return JSON.stringify(cursorData);
210+
export function dataToCursor(data: CursorDataType): string {
211+
return JSON.stringify(data);
203212
}

src/types/sortInputType.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,6 @@ export function prepareSortType(
4545

4646

4747
export function checkSortOpts(key: string, opts: connectionSortOpts) {
48-
if (!opts.resolver) {
49-
throw new Error('You should provide `resolver` option '
50-
+ `for composeWithConnection in opts.sort.${key}`);
51-
}
52-
5348
if (!opts.uniqueFields || !Array.isArray(opts.uniqueFields)) {
5449
throw new Error('You should array of field(s) in `uniqueFields` '
5550
+ `for composeWithConnection in opts.sort.${key}`
@@ -63,10 +58,10 @@ export function checkSortOpts(key: string, opts: connectionSortOpts) {
6358
+ 'Connections does not work without sorting.');
6459
}
6560

66-
if (!opts.cursorToFilter || !isFunction(opts.cursorToFilter)) {
67-
throw new Error('You should provide `cursorToFilter` function '
61+
if (!opts.directionFilter || !isFunction(opts.directionFilter)) {
62+
throw new Error('You should provide `directionFilter` function '
6863
+ `for composeWithConnection in opts.sort.${key}. `
69-
+ 'Connections should have ability to filter record '
70-
+ 'by data dirived from cursor.');
64+
+ 'Connections should have ability to filter '
65+
+ 'forward/backward records started from cursor.');
7166
}
7267
}

0 commit comments

Comments
 (0)