Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 3.6 kB view raw
1/* eslint-disable prefer-rest-params */ 2import { Kind } from '@0no-co/graphql.web'; 3import type { DocumentNode, DefinitionNode } from './utils/graphql'; 4import type { AnyVariables, TypedDocumentNode } from './types'; 5import { keyDocument, stringifyDocument } from './utils'; 6 7/** A GraphQL parse function, which may be called as a tagged template literal, returning a parsed {@link DocumentNode}. 8 * 9 * @remarks 10 * The `gql` tag or function is used to parse a GraphQL query document into a {@link DocumentNode}. 11 * 12 * When used as a tagged template, `gql` will automatically merge fragment definitions into the resulting 13 * document and deduplicate them. 14 * 15 * It enforces that all fragments have a unique name. When fragments with different definitions share a name, 16 * it will log a warning in development. 17 * 18 * Hint: It’s recommended to use this `gql` function over other GraphQL parse functions, since it puts the parsed 19 * results directly into `@urql/core`’s internal caches and prevents further unnecessary work. 20 * 21 * @example 22 * ```ts 23 * const AuthorFragment = gql` 24 * fragment AuthorDisplayComponent on Author { 25 * id 26 * name 27 * } 28 * `; 29 * 30 * const BookFragment = gql` 31 * fragment ListBookComponent on Book { 32 * id 33 * title 34 * author { 35 * ...AuthorDisplayComponent 36 * } 37 * } 38 * 39 * ${AuthorFragment} 40 * `; 41 * 42 * const BookQuery = gql` 43 * query Book($id: ID!) { 44 * book(id: $id) { 45 * ...BookFragment 46 * } 47 * } 48 * 49 * ${BookFragment} 50 * `; 51 * ``` 52 */ 53function gql<Data = any, Variables extends AnyVariables = AnyVariables>( 54 strings: TemplateStringsArray, 55 ...interpolations: Array<TypedDocumentNode | DocumentNode | string> 56): TypedDocumentNode<Data, Variables>; 57 58function gql<Data = any, Variables extends AnyVariables = AnyVariables>( 59 string: string 60): TypedDocumentNode<Data, Variables>; 61 62function gql(parts: string | TemplateStringsArray /* arguments */) { 63 const fragmentNames = new Map<string, string>(); 64 const definitions: DefinitionNode[] = []; 65 const source: DocumentNode[] = []; 66 67 // Apply the entire tagged template body's definitions 68 let body: string = Array.isArray(parts) ? parts[0] : parts || ''; 69 for (let i = 1; i < arguments.length; i++) { 70 const value = arguments[i]; 71 if (value && value.definitions) { 72 source.push(value); 73 } else { 74 body += value; 75 } 76 77 body += arguments[0][i]; 78 } 79 80 source.unshift(keyDocument(body)); 81 for (let i = 0; i < source.length; i++) { 82 for (let j = 0; j < source[i].definitions.length; j++) { 83 const definition = source[i].definitions[j]; 84 if (definition.kind === Kind.FRAGMENT_DEFINITION) { 85 const name = definition.name.value; 86 const value = stringifyDocument(definition); 87 // Fragments will be deduplicated according to this Map 88 if (!fragmentNames.has(name)) { 89 fragmentNames.set(name, value); 90 definitions.push(definition); 91 } else if ( 92 process.env.NODE_ENV !== 'production' && 93 fragmentNames.get(name) !== value 94 ) { 95 // Fragments with the same names is expected to have the same contents 96 console.warn( 97 '[WARNING: Duplicate Fragment] A fragment with name `' + 98 name + 99 '` already exists in this document.\n' + 100 'While fragment names may not be unique across your source, each name must be unique per document.' 101 ); 102 } 103 } else { 104 definitions.push(definition); 105 } 106 } 107 } 108 109 return keyDocument({ 110 kind: Kind.DOCUMENT, 111 definitions, 112 }); 113} 114 115export { gql };