Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 4.1 kB view raw
1import type { 2 FieldNode, 3 SelectionNode, 4 DefinitionNode, 5 DirectiveNode, 6} from '@0no-co/graphql.web'; 7import { Kind } from '@0no-co/graphql.web'; 8import type { KeyedDocumentNode } from './request'; 9import { keyDocument } from './request'; 10import type { FormattedNode, TypedDocumentNode } from '../types'; 11 12const formatNode = < 13 T extends SelectionNode | DefinitionNode | TypedDocumentNode<any, any>, 14>( 15 node: T 16): FormattedNode<T> => { 17 if ('definitions' in node) { 18 const definitions: FormattedNode<DefinitionNode>[] = []; 19 for (let i = 0, l = node.definitions.length; i < l; i++) { 20 const newDefinition = formatNode(node.definitions[i]); 21 definitions.push(newDefinition); 22 } 23 24 return { ...node, definitions } as FormattedNode<T>; 25 } 26 27 if ('directives' in node && node.directives && node.directives.length) { 28 const directives: DirectiveNode[] = []; 29 const _directives = {}; 30 for (let i = 0, l = node.directives.length; i < l; i++) { 31 const directive = node.directives[i]; 32 let name = directive.name.value; 33 if (name[0] !== '_') { 34 directives.push(directive); 35 } else { 36 name = name.slice(1); 37 } 38 _directives[name] = directive; 39 } 40 node = { ...node, directives, _directives }; 41 } 42 43 if ('selectionSet' in node) { 44 const selections: FormattedNode<SelectionNode>[] = []; 45 let hasTypename = node.kind === Kind.OPERATION_DEFINITION; 46 if (node.selectionSet) { 47 for (let i = 0, l = node.selectionSet.selections.length; i < l; i++) { 48 const selection = node.selectionSet.selections[i]; 49 hasTypename = 50 hasTypename || 51 (selection.kind === Kind.FIELD && 52 selection.name.value === '__typename' && 53 !selection.alias); 54 const newSelection = formatNode(selection); 55 selections.push(newSelection); 56 } 57 58 if (!hasTypename) { 59 selections.push({ 60 kind: Kind.FIELD, 61 name: { 62 kind: Kind.NAME, 63 value: '__typename', 64 }, 65 _generated: true, 66 } as FormattedNode<FieldNode>); 67 } 68 69 return { 70 ...node, 71 selectionSet: { ...node.selectionSet, selections }, 72 } as FormattedNode<T>; 73 } 74 } 75 76 return node as FormattedNode<T>; 77}; 78 79const formattedDocs: Map<number, KeyedDocumentNode> = new Map< 80 number, 81 KeyedDocumentNode 82>(); 83 84/** Formats a GraphQL document to add `__typename` fields and process client-side directives. 85 * 86 * @param node - a {@link DocumentNode}. 87 * @returns a {@link FormattedDocument} 88 * 89 * @remarks 90 * Cache {@link Exchange | Exchanges} will require typename introspection to 91 * recognize types in a GraphQL response. To retrieve these typenames, 92 * this function is used to add the `__typename` fields to non-root 93 * selection sets of a GraphQL document. 94 * 95 * Additionally, this utility will process directives, filter out client-side 96 * directives starting with an `_` underscore, and place a `_directives` dictionary 97 * on selection nodes. 98 * 99 * This utility also preserves the internally computed key of the 100 * document as created by {@link createRequest} to avoid any 101 * formatting from being duplicated. 102 * 103 * @see {@link https://spec.graphql.org/October2021/#sec-Type-Name-Introspection} for more information 104 * on typename introspection via the `__typename` field. 105 */ 106export const formatDocument = <T extends TypedDocumentNode<any, any>>( 107 node: T 108): FormattedNode<T> => { 109 const query = keyDocument(node); 110 111 let result = formattedDocs.get(query.__key); 112 if (!result) { 113 formattedDocs.set( 114 query.__key, 115 (result = formatNode(query) as KeyedDocumentNode) 116 ); 117 // Ensure that the hash of the resulting document won't suddenly change 118 // we are marking __key as non-enumerable so when external exchanges use visit 119 // to manipulate a document we won't restore the previous query due to the __key 120 // property. 121 Object.defineProperty(result, '__key', { 122 value: query.__key, 123 enumerable: false, 124 }); 125 } 126 127 return result as FormattedNode<T>; 128};