Skip to content

Commit 789731d

Browse files
committed
Introducing connections
1 parent ab8a49a commit 789731d

File tree

5 files changed

+246
-50
lines changed

5 files changed

+246
-50
lines changed

src/GraphQL/ParseGraphQLSchema.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const RESERVED_GRAPHQL_TYPE_NAMES = [
5151
'UpdateClassPayload',
5252
'DeleteClassInput',
5353
'DeleteClassPayload',
54+
'PageInfo',
5455
];
5556
const RESERVED_GRAPHQL_QUERY_NAMES = ['health', 'viewer', 'class', 'classes'];
5657
const RESERVED_GRAPHQL_MUTATION_NAMES = [
@@ -243,10 +244,16 @@ class ParseGraphQLSchema {
243244
return this.graphQLSchema;
244245
}
245246

246-
addGraphQLType(type, throwError = false, ignoreReserved = false) {
247+
addGraphQLType(
248+
type,
249+
throwError = false,
250+
ignoreReserved = false,
251+
ignoreConnection = false
252+
) {
247253
if (
248254
(!ignoreReserved && RESERVED_GRAPHQL_TYPE_NAMES.includes(type.name)) ||
249-
this.graphQLTypes.find(existingType => existingType.name === type.name)
255+
this.graphQLTypes.find(existingType => existingType.name === type.name) ||
256+
(!ignoreConnection && type.name.endsWith('Connection'))
250257
) {
251258
const message = `Type ${type.name} could not be added to the auto schema because it collided with an existing type.`;
252259
if (throwError) {

src/GraphQL/helpers/objectsQueries.js

Lines changed: 173 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Parse from 'parse/node';
2+
import { offsetToCursor, cursorToOffset } from 'graphql-relay';
23
import rest from '../../rest';
34
import { transformQueryInputToParse } from '../transformers/query';
45

@@ -51,8 +52,11 @@ const findObjects = async (
5152
className,
5253
where,
5354
order,
54-
skip,
55-
limit,
55+
skipInput,
56+
first,
57+
after,
58+
last,
59+
before,
5660
keys,
5761
include,
5862
includeAll,
@@ -70,9 +74,48 @@ const findObjects = async (
7074
}
7175
transformQueryInputToParse(where, fields, className);
7276

77+
const skipAndLimitCalculation = calculateSkipAndLimit(
78+
skipInput,
79+
first,
80+
after,
81+
last,
82+
before,
83+
config.maxLimit
84+
);
85+
let { skip } = skipAndLimitCalculation;
86+
const { limit, needToPreCount } = skipAndLimitCalculation;
87+
let preCount = undefined;
88+
if (needToPreCount) {
89+
const preCountOptions = {
90+
limit: 0,
91+
count: true,
92+
};
93+
if (readPreference) {
94+
preCountOptions.readPreference = readPreference;
95+
}
96+
if (Object.keys(where).length > 0 && subqueryReadPreference) {
97+
preCountOptions.subqueryReadPreference = subqueryReadPreference;
98+
}
99+
preCount = (await rest.find(
100+
config,
101+
auth,
102+
className,
103+
where,
104+
preCountOptions,
105+
info.clientSDK
106+
)).count;
107+
if ((skip || 0) + limit < preCount) {
108+
skip = preCount - limit;
109+
}
110+
}
111+
73112
const options = {};
74113

75-
if (selectedFields.includes('results')) {
114+
if (
115+
selectedFields.find(
116+
field => field.startsWith('edges.') || field.startsWith('pageInfo.')
117+
)
118+
) {
76119
if (limit || limit === 0) {
77120
options.limit = limit;
78121
}
@@ -104,7 +147,12 @@ const findObjects = async (
104147
options.limit = 0;
105148
}
106149

107-
if (selectedFields.includes('count')) {
150+
if (
151+
(selectedFields.includes('count') ||
152+
selectedFields.includes('pageInfo.hasPreviousPage') ||
153+
selectedFields.includes('pageInfo.hasNextPage')) &&
154+
!needToPreCount
155+
) {
108156
options.count = true;
109157
}
110158

@@ -115,14 +163,126 @@ const findObjects = async (
115163
options.subqueryReadPreference = subqueryReadPreference;
116164
}
117165

118-
return await rest.find(
119-
config,
120-
auth,
121-
className,
122-
where,
123-
options,
124-
info.clientSDK
125-
);
166+
let results, count;
167+
if (options.count || !options.limit || (options.limit && options.limit > 0)) {
168+
const findResult = await rest.find(
169+
config,
170+
auth,
171+
className,
172+
where,
173+
options,
174+
info.clientSDK
175+
);
176+
results = findResult.results;
177+
count = findResult.count;
178+
}
179+
180+
let edges = null;
181+
let pageInfo = null;
182+
if (results) {
183+
edges = results.map((result, index) => ({
184+
cursor: offsetToCursor((skip || 0) + index),
185+
node: result,
186+
}));
187+
188+
pageInfo = {
189+
hasPreviousPage:
190+
((preCount && preCount > 0) || (count && count > 0)) &&
191+
skip !== undefined &&
192+
skip > 0,
193+
startCursor: offsetToCursor(skip || 0),
194+
endCursor: offsetToCursor((skip || 0) + (results.length || 1) - 1),
195+
hasNextPage: (preCount || count) > (skip || 0) + results.length,
196+
};
197+
}
198+
199+
return {
200+
edges,
201+
pageInfo,
202+
count: preCount || count,
203+
};
204+
};
205+
206+
const calculateSkipAndLimit = (
207+
skipInput,
208+
first,
209+
after,
210+
last,
211+
before,
212+
maxLimit
213+
) => {
214+
let skip = undefined;
215+
let limit = undefined;
216+
let needToPreCount = false;
217+
if (skipInput || skipInput === 0) {
218+
if (skipInput < 0) {
219+
throw new Parse.Error(
220+
Parse.Error.INVALID_QUERY,
221+
'Skip should be a positive number'
222+
);
223+
}
224+
skip = skipInput;
225+
}
226+
if (after) {
227+
after = cursorToOffset(after);
228+
if ((!after && after !== 0) || after < 0) {
229+
throw new Parse.Error(
230+
Parse.Error.INVALID_QUERY,
231+
'After is not a valid cursor'
232+
);
233+
}
234+
skip = (skip || 0) + (after + 1);
235+
}
236+
if (first || first === 0) {
237+
if (first < 0) {
238+
throw new Parse.Error(
239+
Parse.Error.INVALID_QUERY,
240+
'First should be a positive number'
241+
);
242+
}
243+
limit = first;
244+
}
245+
if (before || before === 0) {
246+
before = cursorToOffset(before);
247+
if ((!before && before !== 0) || before < 0) {
248+
throw new Parse.Error(
249+
Parse.Error.INVALID_QUERY,
250+
'Before is not a valid cursor'
251+
);
252+
}
253+
if ((skip || 0) >= before) {
254+
limit = 0;
255+
} else if ((!limit && limit !== 0) || (skip || 0) + limit > before) {
256+
limit = before - (skip || 0);
257+
}
258+
}
259+
if (last || last === 0) {
260+
if (last < 0) {
261+
throw new Parse.Error(
262+
Parse.Error.INVALID_QUERY,
263+
'Last should be a positive number'
264+
);
265+
}
266+
if (last > maxLimit) {
267+
last = maxLimit;
268+
}
269+
if (limit || limit === 0) {
270+
if (last < limit) {
271+
skip = (skip || 0) + (limit - last);
272+
limit = last;
273+
}
274+
} else if (last === 0) {
275+
limit = 0;
276+
} else {
277+
limit = last;
278+
needToPreCount = true;
279+
}
280+
}
281+
return {
282+
skip,
283+
limit,
284+
needToPreCount,
285+
};
126286
};
127287

128-
export { getObject, findObjects };
288+
export { getObject, findObjects, calculateSkipAndLimit };

src/GraphQL/loaders/parseClassQueries.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,16 @@ const load = function(
9494
),
9595
async resolve(_source, args, context, queryInfo) {
9696
try {
97-
const { where, order, skip, limit, options } = args;
97+
const {
98+
where,
99+
order,
100+
skip,
101+
first,
102+
after,
103+
last,
104+
before,
105+
options,
106+
} = args;
98107
const {
99108
readPreference,
100109
includeReadPreference,
@@ -105,8 +114,8 @@ const load = function(
105114

106115
const { keys, include } = extractKeysAndInclude(
107116
selectedFields
108-
.filter(field => field.includes('.'))
109-
.map(field => field.slice(field.indexOf('.') + 1))
117+
.filter(field => field.startsWith('edges.node.'))
118+
.map(field => field.replace('edges.node.', ''))
110119
);
111120
const parseOrder = order && order.join(',');
112121

@@ -115,7 +124,10 @@ const load = function(
115124
where,
116125
parseOrder,
117126
skip,
118-
limit,
127+
first,
128+
after,
129+
last,
130+
before,
119131
keys,
120132
include,
121133
false,
@@ -125,7 +137,7 @@ const load = function(
125137
config,
126138
auth,
127139
info,
128-
selectedFields.map(field => field.split('.', 1)[0]),
140+
selectedFields,
129141
parseClass.fields
130142
);
131143
} catch (e) {

0 commit comments

Comments
 (0)