Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 3.4 kB view raw
1import type { 2 SelectionNode, 3 DocumentNode, 4 OperationDefinitionNode, 5 FragmentSpreadNode, 6 InlineFragmentNode, 7} from '@0no-co/graphql.web'; 8import { valueFromASTUntyped, Kind } from '@0no-co/graphql.web'; 9 10import type { FormattedNode } from '@urql/core'; 11import { getName, getDirectives } from './node'; 12import { invariant } from '../helpers/help'; 13import type { Fragments, Variables } from '../types'; 14 15function getMainOperation( 16 doc: FormattedNode<DocumentNode> 17): FormattedNode<OperationDefinitionNode>; 18function getMainOperation(doc: DocumentNode): OperationDefinitionNode; 19 20/** Returns the main operation's definition */ 21function getMainOperation(doc: DocumentNode): OperationDefinitionNode { 22 for (let i = 0; i < doc.definitions.length; i++) { 23 if (doc.definitions[i].kind === Kind.OPERATION_DEFINITION) { 24 return doc.definitions[i] as FormattedNode<OperationDefinitionNode>; 25 } 26 } 27 28 invariant( 29 false, 30 'Invalid GraphQL document: All GraphQL documents must contain an OperationDefinition' + 31 'node for a query, subscription, or mutation.', 32 1 33 ); 34} 35 36export { getMainOperation }; 37 38/** Returns a mapping from fragment names to their selections */ 39export const getFragments = (doc: FormattedNode<DocumentNode>): Fragments => { 40 const fragments: Fragments = {}; 41 for (let i = 0; i < doc.definitions.length; i++) { 42 const node = doc.definitions[i]; 43 if (node.kind === Kind.FRAGMENT_DEFINITION) { 44 fragments[getName(node)] = node; 45 } 46 } 47 48 return fragments; 49}; 50 51/** Resolves @include and @skip directives to determine whether field is included. */ 52export const shouldInclude = ( 53 node: FormattedNode<SelectionNode>, 54 vars: Variables 55): boolean => { 56 const directives = getDirectives(node); 57 if (directives.include || directives.skip) { 58 // Finds any @include or @skip directive that forces the node to be skipped 59 for (const name in directives) { 60 const directive = directives[name]; 61 if ( 62 directive && 63 (name === 'include' || name === 'skip') && 64 directive.arguments && 65 directive.arguments[0] && 66 getName(directive.arguments[0]) === 'if' 67 ) { 68 // Return whether this directive forces us to skip 69 // `@include(if: false)` or `@skip(if: true)` 70 const value = valueFromASTUntyped(directive.arguments[0].value, vars); 71 return name === 'include' ? !!value : !value; 72 } 73 } 74 } 75 return true; 76}; 77 78/** Resolves @defer directive to determine whether a fragment is potentially skipped. */ 79export const isDeferred = ( 80 node: FormattedNode<FragmentSpreadNode | InlineFragmentNode>, 81 vars: Variables 82): boolean => { 83 const { defer } = getDirectives(node); 84 if (defer) { 85 for (const argument of defer.arguments || []) { 86 if (getName(argument) === 'if') { 87 // Return whether `@defer(if: )` is enabled 88 return !!valueFromASTUntyped(argument.value, vars); 89 } 90 } 91 return true; 92 } 93 94 return false; 95}; 96 97/** Resolves @_optional and @_required directive to determine whether the fields in a fragment are conaidered optional. */ 98export const isOptional = ( 99 node: FormattedNode<FragmentSpreadNode | InlineFragmentNode> 100): boolean | undefined => { 101 const { optional, required } = getDirectives(node); 102 if (required) { 103 return false; 104 } 105 106 if (optional) { 107 return true; 108 } 109 110 return undefined; 111};