feat: replace GraphQL Apollo with GraphQL Yoga (#7967)
This commit is contained in:
@@ -89,13 +89,14 @@ class ParseGraphQLSchema {
|
||||
this.graphQLCustomTypeDefs = params.graphQLCustomTypeDefs;
|
||||
this.appId = params.appId || requiredParameter('You must provide the appId!');
|
||||
this.schemaCache = SchemaCache;
|
||||
this.logCache = {};
|
||||
}
|
||||
|
||||
async load() {
|
||||
const { parseGraphQLConfig } = await this._initializeSchemaAndConfig();
|
||||
const parseClassesArray = await this._getClassesForSchema(parseGraphQLConfig);
|
||||
const functionNames = await this._getFunctionNames();
|
||||
const functionNamesString = JSON.stringify(functionNames);
|
||||
const functionNamesString = functionNames.join();
|
||||
|
||||
const parseClasses = parseClassesArray.reduce((acc, clazz) => {
|
||||
acc[clazz.className] = clazz;
|
||||
@@ -331,6 +332,14 @@ class ParseGraphQLSchema {
|
||||
return this.graphQLSchema;
|
||||
}
|
||||
|
||||
_logOnce(severity, message) {
|
||||
if (this.logCache[message]) {
|
||||
return;
|
||||
}
|
||||
this.log[severity](message);
|
||||
this.logCache[message] = true;
|
||||
}
|
||||
|
||||
addGraphQLType(type, throwError = false, ignoreReserved = false, ignoreConnection = false) {
|
||||
if (
|
||||
(!ignoreReserved && RESERVED_GRAPHQL_TYPE_NAMES.includes(type.name)) ||
|
||||
@@ -341,7 +350,7 @@ class ParseGraphQLSchema {
|
||||
if (throwError) {
|
||||
throw new Error(message);
|
||||
}
|
||||
this.log.warn(message);
|
||||
this._logOnce('warn', message);
|
||||
return undefined;
|
||||
}
|
||||
this.graphQLTypes.push(type);
|
||||
@@ -357,7 +366,7 @@ class ParseGraphQLSchema {
|
||||
if (throwError) {
|
||||
throw new Error(message);
|
||||
}
|
||||
this.log.warn(message);
|
||||
this._logOnce('warn', message);
|
||||
return undefined;
|
||||
}
|
||||
this.graphQLQueries[fieldName] = field;
|
||||
@@ -373,7 +382,7 @@ class ParseGraphQLSchema {
|
||||
if (throwError) {
|
||||
throw new Error(message);
|
||||
}
|
||||
this.log.warn(message);
|
||||
this._logOnce('warn', message);
|
||||
return undefined;
|
||||
}
|
||||
this.graphQLMutations[fieldName] = field;
|
||||
@@ -482,7 +491,8 @@ class ParseGraphQLSchema {
|
||||
if (/^[_a-zA-Z][_a-zA-Z0-9]*$/.test(functionName)) {
|
||||
return true;
|
||||
} else {
|
||||
this.log.warn(
|
||||
this._logOnce(
|
||||
'warn',
|
||||
`Function ${functionName} could not be added to the auto schema because GraphQL names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/.`
|
||||
);
|
||||
return false;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import corsMiddleware from 'cors';
|
||||
import bodyParser from 'body-parser';
|
||||
import { graphqlUploadExpress } from 'graphql-upload';
|
||||
import { graphqlExpress } from 'apollo-server-express/dist/expressApollo';
|
||||
import { renderPlaygroundPage } from '@apollographql/graphql-playground-html';
|
||||
import { createServer, renderGraphiQL } from '@graphql-yoga/node';
|
||||
import { execute, subscribe } from 'graphql';
|
||||
import { SubscriptionServer } from 'subscriptions-transport-ws';
|
||||
import { handleParseErrors, handleParseHeaders } from '../middlewares';
|
||||
@@ -32,18 +29,19 @@ class ParseGraphQLServer {
|
||||
});
|
||||
}
|
||||
|
||||
async _getGraphQLOptions(req) {
|
||||
async _getGraphQLOptions() {
|
||||
try {
|
||||
return {
|
||||
schema: await this.parseGraphQLSchema.load(),
|
||||
context: {
|
||||
info: req.info,
|
||||
config: req.config,
|
||||
auth: req.auth,
|
||||
},
|
||||
formatError: error => {
|
||||
// Allow to console.log here to debug
|
||||
return error;
|
||||
context: ({ req: { info, config, auth } }) => ({
|
||||
info,
|
||||
config,
|
||||
auth,
|
||||
}),
|
||||
multipart: {
|
||||
fileSize: this._transformMaxUploadSizeToBytes(
|
||||
this.parseServer.config.maxUploadSize || '20mb'
|
||||
),
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
@@ -52,6 +50,17 @@ class ParseGraphQLServer {
|
||||
}
|
||||
}
|
||||
|
||||
async _getServer() {
|
||||
const schemaRef = this.parseGraphQLSchema.graphQLSchema;
|
||||
const newSchemaRef = await this.parseGraphQLSchema.load();
|
||||
if (schemaRef === newSchemaRef && this._server) {
|
||||
return this._server;
|
||||
}
|
||||
const options = await this._getGraphQLOptions();
|
||||
this._server = createServer(options);
|
||||
return this._server;
|
||||
}
|
||||
|
||||
_transformMaxUploadSizeToBytes(maxUploadSize) {
|
||||
const unitMap = {
|
||||
kb: 1,
|
||||
@@ -70,22 +79,13 @@ class ParseGraphQLServer {
|
||||
requiredParameter('You must provide an Express.js app instance!');
|
||||
}
|
||||
|
||||
app.use(
|
||||
this.config.graphQLPath,
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: this._transformMaxUploadSizeToBytes(
|
||||
this.parseServer.config.maxUploadSize || '20mb'
|
||||
),
|
||||
})
|
||||
);
|
||||
app.use(this.config.graphQLPath, corsMiddleware());
|
||||
app.use(this.config.graphQLPath, bodyParser.json());
|
||||
app.use(this.config.graphQLPath, handleParseHeaders);
|
||||
app.use(this.config.graphQLPath, handleParseErrors);
|
||||
app.use(
|
||||
this.config.graphQLPath,
|
||||
graphqlExpress(async req => await this._getGraphQLOptions(req))
|
||||
);
|
||||
app.use(this.config.graphQLPath, async (req, res) => {
|
||||
const server = await this._getServer();
|
||||
return server(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
applyPlayground(app) {
|
||||
@@ -98,14 +98,13 @@ class ParseGraphQLServer {
|
||||
(_req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(
|
||||
renderPlaygroundPage({
|
||||
renderGraphiQL({
|
||||
endpoint: this.config.graphQLPath,
|
||||
version: '1.7.25',
|
||||
subscriptionEndpoint: this.config.subscriptionsPath,
|
||||
headers: {
|
||||
headers: JSON.stringify({
|
||||
'X-Parse-Application-Id': this.parseServer.config.appId,
|
||||
'X-Parse-Master-Key': this.parseServer.config.masterKey,
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
res.end();
|
||||
|
||||
@@ -50,7 +50,7 @@ const getObject = async (
|
||||
options.keys = keys;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.error(e);
|
||||
}
|
||||
if (include) {
|
||||
options.include = include;
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
GraphQLUnionType,
|
||||
} from 'graphql';
|
||||
import { toGlobalId } from 'graphql-relay';
|
||||
import { GraphQLUpload } from '@graphql-tools/links';
|
||||
|
||||
class TypeValidationError extends Error {
|
||||
constructor(value, type) {
|
||||
@@ -223,6 +222,11 @@ const DATE = new GraphQLScalarType({
|
||||
},
|
||||
});
|
||||
|
||||
const GraphQLUpload = new GraphQLScalarType({
|
||||
name: 'Upload',
|
||||
description: 'The Upload scalar type represents a file upload.',
|
||||
});
|
||||
|
||||
const BYTES = new GraphQLScalarType({
|
||||
name: 'Bytes',
|
||||
description:
|
||||
@@ -1265,6 +1269,7 @@ const load = parseGraphQLSchema => {
|
||||
};
|
||||
|
||||
export {
|
||||
GraphQLUpload,
|
||||
TypeValidationError,
|
||||
parseStringValue,
|
||||
parseIntValue,
|
||||
|
||||
@@ -1,43 +1,33 @@
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||
import { GraphQLUpload } from '@graphql-tools/links';
|
||||
import Parse from 'parse/node';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import logger from '../../logger';
|
||||
|
||||
const handleUpload = async (upload, config) => {
|
||||
const { createReadStream, filename, mimetype } = await upload;
|
||||
let data = null;
|
||||
if (createReadStream) {
|
||||
const stream = createReadStream();
|
||||
data = await new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
stream
|
||||
.on('error', reject)
|
||||
.on('data', chunk => chunks.push(chunk))
|
||||
.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
}
|
||||
const data = Buffer.from(await upload.arrayBuffer());
|
||||
const fileName = upload.name;
|
||||
const type = upload.type;
|
||||
|
||||
if (!data || !data.length) {
|
||||
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.');
|
||||
}
|
||||
|
||||
if (filename.length > 128) {
|
||||
if (fileName.length > 128) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename too long.');
|
||||
}
|
||||
|
||||
if (!filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
|
||||
if (!fileName.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename contains invalid characters.');
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
fileInfo: await config.filesController.createFile(config, filename, data, mimetype),
|
||||
fileInfo: await config.filesController.createFile(config, fileName, data, type),
|
||||
};
|
||||
} catch (e) {
|
||||
logger.error('Error creating a file: ', e);
|
||||
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, `Could not store file: ${filename}.`);
|
||||
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, `Could not store file: ${fileName}.`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,7 +38,7 @@ const load = parseGraphQLSchema => {
|
||||
inputFields: {
|
||||
upload: {
|
||||
description: 'This is the new file to be created and uploaded.',
|
||||
type: new GraphQLNonNull(GraphQLUpload),
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.GraphQLUpload),
|
||||
},
|
||||
},
|
||||
outputFields: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Parse from 'parse/node';
|
||||
import { ApolloError } from 'apollo-server-core';
|
||||
import { GraphQLYogaError } from '@graphql-yoga/node';
|
||||
|
||||
export function enforceMasterKeyAccess(auth) {
|
||||
if (!auth.isMaster) {
|
||||
@@ -16,7 +16,7 @@ export function toGraphQLError(error) {
|
||||
code = Parse.Error.INTERNAL_SERVER_ERROR;
|
||||
message = 'Internal server error';
|
||||
}
|
||||
return new ApolloError(message, code);
|
||||
return new GraphQLYogaError(message, { code });
|
||||
}
|
||||
|
||||
export const extractKeysAndInclude = selectedFields => {
|
||||
|
||||
Reference in New Issue
Block a user