diff --git a/package.json b/package.json index a9f5cb6e..c1df4bc9 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,13 @@ "private": true, "main": "index.js", "types": "index.d.ts", - "homepage": "https://github.com/graphql/express-graphql", + "homepage": "https://github.com/Ariel-Dayan/express-graphql.git", "bugs": { "url": "https://github.com/graphql/express-graphql/issues" }, "repository": { "type": "git", - "url": "https://github.com/graphql/express-graphql.git" + "url": "https://github.com/Ariel-Dayan/express-graphql.git" }, "keywords": [ "express", diff --git a/src/index.js b/src/index.js index 72f98371..98296965 100644 --- a/src/index.js +++ b/src/index.js @@ -140,6 +140,11 @@ export type OptionsData = {| * `__typename` field or alternatively calls the `isTypeOf` method). */ typeResolver?: ?GraphQLTypeResolver, + + /** + * A optional string which will used to predefined fragments + */ + serverFragments?: ?string, |}; /** @@ -238,6 +243,8 @@ function graphqlHTTP(options: Options): Middleware { const typeResolver = optionsData.typeResolver; const validationRules = optionsData.validationRules || []; const graphiql = optionsData.graphiql; + const serverFragments = optionsData.serverFragments; + context = optionsData.context || request; // GraphQL HTTP only supports GET and POST methods. @@ -247,7 +254,7 @@ function graphqlHTTP(options: Options): Middleware { } // Get GraphQL params from the request and POST body data. - query = params.query; + query = serverFragments ? addServerFragments(serverFragments, params.query) : params.query; variables = params.variables; operationName = params.operationName; showGraphiQL = canDisplayGraphiQL(request, params) && graphiql; @@ -509,3 +516,87 @@ function sendResponse(response: $Response, type: string, data: string): void { response.setHeader('Content-Length', String(chunk.length)); response.end(chunk); } + +/** + * Helper function to get the first word from string. + * + * @param { string } text - The full string. + * + * @returns { string } - First word. + */ +function sliceFirstWord(text: string): string { + let slicedText = text; + + const firstSpaceIndex = slicedText.indexOf(' '); + + if(firstSpaceIndex !== -1) { + slicedText = slicedText.slice(0, firstSpaceIndex); + } + + const firstEndRowIndex = slicedText.indexOf('\n'); + + if(firstEndRowIndex !== -1) { + slicedText = slicedText.slice(0, firstEndRowIndex); + } + + return slicedText; +} + +/** + * Helper recursive function that finds all the fragments from the server that are used in the current request. + * + * @param { string } serverFragments - Fragments from the server. + * @param { string } query - Query from the request. + * @param { Set } fragmentsInUsed - Set of relevant fragments. + * + * @returns { Set } - relevant fragments for current request. + */ +function findFragments(serverFragments: string, query: string, fragmentsInUsed: Set): Set { + // Fragment declaration starts with 'fragment' key word + // Slice to remove text before the first fragment declaration + let fragmentDeclarationFields = serverFragments.split('fragment ').slice(1); + + // Fragment variable starts with spread - '...' + // Slice to remove text before the first fragment variable + let fragmentVariableFields = query.split('...').slice(1); + + fragmentVariableFields.forEach(fragmentVariable => { + const currFragmentVariableKeyName = sliceFirstWord(fragmentVariable); + + for (let index = 0; index < fragmentDeclarationFields.length; index++) { + const currFragmentDeclaration = fragmentDeclarationFields[index]; + const currFragmentDeclarationKeyName = sliceFirstWord(currFragmentDeclaration); + + if(currFragmentDeclarationKeyName === currFragmentVariableKeyName) { + + fragmentsInUsed.add(currFragmentDeclaration); + + // Find fragments in the matching fragments + fragmentsInUsed = findFragments(serverFragments, currFragmentDeclaration, fragmentsInUsed) + + break; + } + + } + }) + + return fragmentsInUsed; +} + +/** + * Add to query the relevant server fragments + * + * @param {*} serverFragments - Fragments from the server. + * @param {*} query - Query from the request. + * + * @returns - Concat relevant fragments to request query + */ +function addServerFragments(serverFragments: string, query: string): string { + let fragmentsInUsed = ''; + + [...(findFragments(serverFragments, query, new Set()))].forEach(fullFragment => { + fragmentsInUsed += `fragment ${fullFragment} `; + }) + + return `${fragmentsInUsed}\n${query}`; +} \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts index 1a7e9495..82532c05 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -137,6 +137,11 @@ declare namespace graphqlHTTP { * `__typename` field or alternatively calls the `isTypeOf` method). */ typeResolver?: GraphQLTypeResolver | null; + + /** + * A optional string which will used to predefined fragments + */ + serverFragments?: string; } /**