Skip to content

Commit

Permalink
feat: replace GraphQL Apollo with GraphQL Yoga (parse-community#7967)
Browse files Browse the repository at this point in the history
  • Loading branch information
Moumouls authored May 18, 2022
1 parent b2ae2e1 commit 1aa2204
Show file tree
Hide file tree
Showing 9 changed files with 516 additions and 1,171 deletions.
1,372 changes: 319 additions & 1,053 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,11 @@
],
"license": "BSD-3-Clause",
"dependencies": {
"@apollo/client": "3.5.10",
"@apollographql/graphql-playground-html": "1.6.29",
"@graphql-tools/links": "8.2.11",
"@graphql-tools/stitch": "6.2.4",
"@graphql-tools/utils": "6.2.4",
"@graphql-yoga/node": "2.6.0",
"@parse/fs-files-adapter": "1.2.2",
"@parse/push-adapter": "4.1.2",
"apollo-server-express": "2.25.2",
"bcryptjs": "2.4.3",
"body-parser": "1.20.0",
"commander": "5.1.0",
Expand All @@ -38,7 +35,6 @@
"graphql-list-fields": "2.0.2",
"graphql-relay": "0.7.0",
"graphql-tag": "2.12.6",
"graphql-upload": "11.0.0",
"intersect": "1.0.1",
"jsonwebtoken": "8.5.1",
"jwks-rsa": "2.0.5",
Expand All @@ -63,6 +59,7 @@
},
"devDependencies": {
"@actions/core": "1.2.6",
"@apollo/client": "3.6.1",
"@babel/cli": "7.10.0",
"@babel/core": "7.10.0",
"@babel/plugin-proposal-object-rest-spread": "7.10.0",
Expand Down Expand Up @@ -98,7 +95,7 @@
"mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter",
"mongodb-runner": "4.8.1",
"mongodb-version-list": "1.0.0",
"node-fetch": "3.1.1",
"node-fetch": "3.2.4",
"nyc": "15.1.0",
"prettier": "2.0.5",
"semantic-release": "17.4.6",
Expand Down
188 changes: 133 additions & 55 deletions spec/ParseGraphQLServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,24 @@ describe('ParseGraphQLServer', () => {
});
});

describe('_getServer', () => {
it('should only return new server on schema changes', async () => {
parseGraphQLServer.server = undefined;
const server1 = await parseGraphQLServer._getServer();
const server2 = await parseGraphQLServer._getServer();
expect(server1).toBe(server2);

// Trigger a schema change
const obj = new Parse.Object('SomeClass');
await obj.save();

const server3 = await parseGraphQLServer._getServer();
const server4 = await parseGraphQLServer._getServer();
expect(server3).not.toBe(server2);
expect(server3).toBe(server4);
});
});

describe('_getGraphQLOptions', () => {
const req = {
info: new Object(),
Expand All @@ -106,11 +124,15 @@ describe('ParseGraphQLServer', () => {
};

it("should return schema and context with req's info, config and auth", async () => {
const options = await parseGraphQLServer._getGraphQLOptions(req);
const options = await parseGraphQLServer._getGraphQLOptions();
expect(options.multipart).toEqual({
fileSize: 20971520,
});
expect(options.schema).toEqual(parseGraphQLServer.parseGraphQLSchema.graphQLSchema);
expect(options.context.info).toEqual(req.info);
expect(options.context.config).toEqual(req.config);
expect(options.context.auth).toEqual(req.auth);
const contextResponse = options.context({ req });
expect(contextResponse.info).toEqual(req.info);
expect(contextResponse.config).toEqual(req.config);
expect(contextResponse.auth).toEqual(req.auth);
});

it('should load GraphQL schema in every call', async () => {
Expand Down Expand Up @@ -467,7 +489,7 @@ describe('ParseGraphQLServer', () => {
}
});

it('should be cors enabled', async () => {
it('should be cors enabled and scope the response within the source origin', async () => {
let checked = false;
const apolloClient = new ApolloClient({
link: new ApolloLink((operation, forward) => {
Expand All @@ -476,7 +498,7 @@ describe('ParseGraphQLServer', () => {
const {
response: { headers },
} = context;
expect(headers.get('access-control-allow-origin')).toEqual('*');
expect(headers.get('access-control-allow-origin')).toEqual('http://example.com');
checked = true;
return response;
});
Expand All @@ -486,7 +508,7 @@ describe('ParseGraphQLServer', () => {
fetch,
headers: {
...headers,
Origin: 'http://someorigin.com',
Origin: 'http://example.com',
},
})
),
Expand All @@ -504,14 +526,25 @@ describe('ParseGraphQLServer', () => {
});

it('should handle Parse headers', async () => {
let checked = false;
const test = {
context: ({ req: { info, config, auth } }) => {
expect(req.info).toBeDefined();
expect(req.config).toBeDefined();
expect(req.auth).toBeDefined();
return {
info,
config,
auth,
};
},
};
const contextSpy = spyOn(test, 'context');
const originalGetGraphQLOptions = parseGraphQLServer._getGraphQLOptions;
parseGraphQLServer._getGraphQLOptions = async req => {
expect(req.info).toBeDefined();
expect(req.config).toBeDefined();
expect(req.auth).toBeDefined();
checked = true;
return await originalGetGraphQLOptions.bind(parseGraphQLServer)(req);
parseGraphQLServer._getGraphQLOptions = async () => {
return {
schema: await parseGraphQLServer.parseGraphQLSchema.load(),
context: test.context,
};
};
const health = (
await apolloClient.query({
Expand All @@ -523,7 +556,7 @@ describe('ParseGraphQLServer', () => {
})
).data.health;
expect(health).toBeTruthy();
expect(checked).toBeTruthy();
expect(contextSpy).toHaveBeenCalledTimes(1);
parseGraphQLServer._getGraphQLOptions = originalGetGraphQLOptions;
});
});
Expand Down Expand Up @@ -6786,7 +6819,7 @@ describe('ParseGraphQLServer', () => {

expect(queryResult.data.customers.edges.length).toEqual(1);
} catch (e) {
console.log(JSON.stringify(e));
console.error(JSON.stringify(e));
}
});
});
Expand Down Expand Up @@ -9107,15 +9140,15 @@ describe('ParseGraphQLServer', () => {
'operations',
JSON.stringify({
query: `
mutation CreateFile($input: CreateFileInput!) {
createFile(input: $input) {
fileInfo {
name
url
mutation CreateFile($input: CreateFileInput!) {
createFile(input: $input) {
fileInfo {
name
url
}
}
}
}
`,
`,
variables: {
input: {
upload: null,
Expand Down Expand Up @@ -9176,46 +9209,46 @@ describe('ParseGraphQLServer', () => {
'operations',
JSON.stringify({
query: `
mutation CreateSomeObject(
$fields1: CreateSomeClassFieldsInput
$fields2: CreateSomeClassFieldsInput
$fields3: CreateSomeClassFieldsInput
) {
createSomeClass1: createSomeClass(
input: { fields: $fields1 }
mutation CreateSomeObject(
$fields1: CreateSomeClassFieldsInput
$fields2: CreateSomeClassFieldsInput
$fields3: CreateSomeClassFieldsInput
) {
someClass {
id
someField {
name
url
createSomeClass1: createSomeClass(
input: { fields: $fields1 }
) {
someClass {
id
someField {
name
url
}
}
}
}
createSomeClass2: createSomeClass(
input: { fields: $fields2 }
) {
someClass {
id
someField {
name
url
createSomeClass2: createSomeClass(
input: { fields: $fields2 }
) {
someClass {
id
someField {
name
url
}
}
}
}
createSomeClass3: createSomeClass(
input: { fields: $fields3 }
) {
someClass {
id
someField {
name
url
createSomeClass3: createSomeClass(
input: { fields: $fields3 }
) {
someClass {
id
someField {
name
url
}
}
}
}
}
`,
`,
variables: {
fields1: {
someField: { file: someFieldValue },
Expand Down Expand Up @@ -9344,6 +9377,51 @@ describe('ParseGraphQLServer', () => {
}
});

it_only_node_version('<17')('should not upload if file is too large', async () => {
parseGraphQLServer.parseServer.config.maxUploadSize = '1kb';

const body = new FormData();
body.append(
'operations',
JSON.stringify({
query: `
mutation CreateFile($input: CreateFileInput!) {
createFile(input: $input) {
fileInfo {
name
url
}
}
}
`,
variables: {
input: {
upload: null,
},
},
})
);
body.append('map', JSON.stringify({ 1: ['variables.input.upload'] }));
body.append(
'1',
Buffer.alloc(parseGraphQLServer._transformMaxUploadSizeToBytes('2kb'), 1),
{
filename: 'myFileName.txt',
contentType: 'text/plain',
}
);

const res = await fetch('http://localhost:13377/graphql', {
method: 'POST',
headers,
body,
});

const result = JSON.parse(await res.text());
expect(res.status).toEqual(500);
expect(result.errors[0].message).toEqual('File size limit exceeded: 1024 bytes');
});

it('should support object values', async () => {
try {
const someObjectFieldValue = {
Expand Down
20 changes: 15 additions & 5 deletions src/GraphQL/ParseGraphQLSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)) ||
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 1aa2204

Please sign in to comment.