Files
kami-parse-server/src/GraphQL/helpers/objectsQueries.js
Antoine Cormouls c7f96c92cd GraphQL: Allow true GraphQL Schema Customization (#6360)
* Allow real GraphQL Schema via ParseServer.start

* wip

* working

* tests ok

* add tests about enum/input use case

* Add async function based merge

* Better naming

* remove useless condition
2020-02-21 15:12:49 -08:00

330 lines
7.6 KiB
JavaScript

import Parse from 'parse/node';
import { offsetToCursor, cursorToOffset } from 'graphql-relay';
import rest from '../../rest';
import { transformQueryInputToParse } from '../transformers/query';
const needToGetAllKeys = (fields, keys) =>
keys
? !!keys.split(',').find(keyName => !fields[keyName.split('.')[0]])
: true;
const getObject = async (
className,
objectId,
keys,
include,
readPreference,
includeReadPreference,
config,
auth,
info,
parseClass
) => {
const options = {};
if (!needToGetAllKeys(parseClass.fields, keys)) {
options.keys = keys;
}
if (include) {
options.include = include;
if (includeReadPreference) {
options.includeReadPreference = includeReadPreference;
}
}
if (readPreference) {
options.readPreference = readPreference;
}
const response = await rest.get(
config,
auth,
className,
objectId,
options,
info.clientSDK
);
if (!response.results || response.results.length == 0) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
const object = response.results[0];
if (className === '_User') {
delete object.sessionToken;
}
return object;
};
const findObjects = async (
className,
where,
order,
skipInput,
first,
after,
last,
before,
keys,
include,
includeAll,
readPreference,
includeReadPreference,
subqueryReadPreference,
config,
auth,
info,
selectedFields,
parseClasses
) => {
if (!where) {
where = {};
}
transformQueryInputToParse(where, className, parseClasses);
const skipAndLimitCalculation = calculateSkipAndLimit(
skipInput,
first,
after,
last,
before,
config.maxLimit
);
let { skip } = skipAndLimitCalculation;
const { limit, needToPreCount } = skipAndLimitCalculation;
let preCount = undefined;
if (needToPreCount) {
const preCountOptions = {
limit: 0,
count: true,
};
if (readPreference) {
preCountOptions.readPreference = readPreference;
}
if (Object.keys(where).length > 0 && subqueryReadPreference) {
preCountOptions.subqueryReadPreference = subqueryReadPreference;
}
preCount = (
await rest.find(
config,
auth,
className,
where,
preCountOptions,
info.clientSDK
)
).count;
if ((skip || 0) + limit < preCount) {
skip = preCount - limit;
}
}
const options = {};
if (
selectedFields.find(
field => field.startsWith('edges.') || field.startsWith('pageInfo.')
)
) {
if (limit || limit === 0) {
options.limit = limit;
} else {
options.limit = 100;
}
if (options.limit !== 0) {
if (order) {
options.order = order;
}
if (skip) {
options.skip = skip;
}
if (config.maxLimit && options.limit > config.maxLimit) {
// Silently replace the limit on the query with the max configured
options.limit = config.maxLimit;
}
if (
!needToGetAllKeys(
parseClasses.find(
({ className: parseClassName }) => className === parseClassName
).fields,
keys
)
) {
options.keys = keys;
}
if (includeAll === true) {
options.includeAll = includeAll;
}
if (!options.includeAll && include) {
options.include = include;
}
if ((options.includeAll || options.include) && includeReadPreference) {
options.includeReadPreference = includeReadPreference;
}
}
} else {
options.limit = 0;
}
if (
(selectedFields.includes('count') ||
selectedFields.includes('pageInfo.hasPreviousPage') ||
selectedFields.includes('pageInfo.hasNextPage')) &&
!needToPreCount
) {
options.count = true;
}
if (readPreference) {
options.readPreference = readPreference;
}
if (Object.keys(where).length > 0 && subqueryReadPreference) {
options.subqueryReadPreference = subqueryReadPreference;
}
let results, count;
if (options.count || !options.limit || (options.limit && options.limit > 0)) {
const findResult = await rest.find(
config,
auth,
className,
where,
options,
info.clientSDK
);
results = findResult.results;
count = findResult.count;
}
let edges = null;
let pageInfo = null;
if (results) {
edges = results.map((result, index) => ({
cursor: offsetToCursor((skip || 0) + index),
node: result,
}));
pageInfo = {
hasPreviousPage:
((preCount && preCount > 0) || (count && count > 0)) &&
skip !== undefined &&
skip > 0,
startCursor: offsetToCursor(skip || 0),
endCursor: offsetToCursor((skip || 0) + (results.length || 1) - 1),
hasNextPage: (preCount || count) > (skip || 0) + results.length,
};
}
return {
edges,
pageInfo,
count: preCount || count,
};
};
const calculateSkipAndLimit = (
skipInput,
first,
after,
last,
before,
maxLimit
) => {
let skip = undefined;
let limit = undefined;
let needToPreCount = false;
// Validates the skip input
if (skipInput || skipInput === 0) {
if (skipInput < 0) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'Skip should be a positive number'
);
}
skip = skipInput;
}
// Validates the after param
if (after) {
after = cursorToOffset(after);
if ((!after && after !== 0) || after < 0) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'After is not a valid cursor'
);
}
// If skip and after are passed, a new skip is calculated by adding them
skip = (skip || 0) + (after + 1);
}
// Validates the first param
if (first || first === 0) {
if (first < 0) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'First should be a positive number'
);
}
// The first param is translated to the limit param of the Parse legacy API
limit = first;
}
// Validates the before param
if (before || before === 0) {
// This method converts the cursor to the index of the object
before = cursorToOffset(before);
if ((!before && before !== 0) || before < 0) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'Before is not a valid cursor'
);
}
if ((skip || 0) >= before) {
// If the before index is less then the skip, no objects will be returned
limit = 0;
} else if ((!limit && limit !== 0) || (skip || 0) + limit > before) {
// If there is no limit set, the limit is calculated. Or, if the limit (plus skip) is bigger than the before index, the new limit is set.
limit = before - (skip || 0);
}
}
// Validates the last param
if (last || last === 0) {
if (last < 0) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'Last should be a positive number'
);
}
if (last > maxLimit) {
// Last can't be bigger than Parse server maxLimit config.
last = maxLimit;
}
if (limit || limit === 0) {
// If there is a previous limit set, it may be adjusted
if (last < limit) {
// if last is less than the current limit
skip = (skip || 0) + (limit - last); // The skip is adjusted
limit = last; // the limit is adjusted
}
} else if (last === 0) {
// No objects will be returned
limit = 0;
} else {
// No previous limit set, the limit will be equal to last and pre count is needed.
limit = last;
needToPreCount = true;
}
}
return {
skip,
limit,
needToPreCount,
};
};
export { getObject, findObjects, calculateSkipAndLimit, needToGetAllKeys };