UNPKG

6.48 kBPlain TextView Raw
1import {
2 DocumentNode,
3 OperationDefinitionNode,
4 FragmentDefinitionNode,
5 ValueNode,
6} from 'graphql';
7
8import { invariant, InvariantError } from 'ts-invariant';
9
10import { assign } from './util/assign';
11
12import { valueToObjectRepresentation, JsonValue } from './storeUtils';
13
14export function getMutationDefinition(
15 doc: DocumentNode,
16): OperationDefinitionNode {
17 checkDocument(doc);
18
19 let mutationDef: OperationDefinitionNode | null = doc.definitions.filter(
20 definition =>
21 definition.kind === 'OperationDefinition' &&
22 definition.operation === 'mutation',
23 )[0] as OperationDefinitionNode;
24
25 invariant(mutationDef, 'Must contain a mutation definition.');
26
27 return mutationDef;
28}
29
30// Checks the document for errors and throws an exception if there is an error.
31export function checkDocument(doc: DocumentNode) {
32 invariant(
33 doc && doc.kind === 'Document',
34 `Expecting a parsed GraphQL document. Perhaps you need to wrap the query \
35string in a "gql" tag? http://docs.apollostack.com/apollo-client/core.html#gql`,
36 );
37
38 const operations = doc.definitions
39 .filter(d => d.kind !== 'FragmentDefinition')
40 .map(definition => {
41 if (definition.kind !== 'OperationDefinition') {
42 throw new InvariantError(
43 `Schema type definitions not allowed in queries. Found: "${
44 definition.kind
45 }"`,
46 );
47 }
48 return definition;
49 });
50
51 invariant(
52 operations.length <= 1,
53 `Ambiguous GraphQL document: contains ${operations.length} operations`,
54 );
55
56 return doc;
57}
58
59export function getOperationDefinition(
60 doc: DocumentNode,
61): OperationDefinitionNode | undefined {
62 checkDocument(doc);
63 return doc.definitions.filter(
64 definition => definition.kind === 'OperationDefinition',
65 )[0] as OperationDefinitionNode;
66}
67
68export function getOperationDefinitionOrDie(
69 document: DocumentNode,
70): OperationDefinitionNode {
71 const def = getOperationDefinition(document);
72 invariant(def, `GraphQL document is missing an operation`);
73 return def;
74}
75
76export function getOperationName(doc: DocumentNode): string | null {
77 return (
78 doc.definitions
79 .filter(
80 definition =>
81 definition.kind === 'OperationDefinition' && definition.name,
82 )
83 .map((x: OperationDefinitionNode) => x.name.value)[0] || null
84 );
85}
86
87// Returns the FragmentDefinitions from a particular document as an array
88export function getFragmentDefinitions(
89 doc: DocumentNode,
90): FragmentDefinitionNode[] {
91 return doc.definitions.filter(
92 definition => definition.kind === 'FragmentDefinition',
93 ) as FragmentDefinitionNode[];
94}
95
96export function getQueryDefinition(doc: DocumentNode): OperationDefinitionNode {
97 const queryDef = getOperationDefinition(doc) as OperationDefinitionNode;
98
99 invariant(
100 queryDef && queryDef.operation === 'query',
101 'Must contain a query definition.',
102 );
103
104 return queryDef;
105}
106
107export function getFragmentDefinition(
108 doc: DocumentNode,
109): FragmentDefinitionNode {
110 invariant(
111 doc.kind === 'Document',
112 `Expecting a parsed GraphQL document. Perhaps you need to wrap the query \
113string in a "gql" tag? http://docs.apollostack.com/apollo-client/core.html#gql`,
114 );
115
116 invariant(
117 doc.definitions.length <= 1,
118 'Fragment must have exactly one definition.',
119 );
120
121 const fragmentDef = doc.definitions[0] as FragmentDefinitionNode;
122
123 invariant(
124 fragmentDef.kind === 'FragmentDefinition',
125 'Must be a fragment definition.',
126 );
127
128 return fragmentDef as FragmentDefinitionNode;
129}
130
131/**
132 * Returns the first operation definition found in this document.
133 * If no operation definition is found, the first fragment definition will be returned.
134 * If no definitions are found, an error will be thrown.
135 */
136export function getMainDefinition(
137 queryDoc: DocumentNode,
138): OperationDefinitionNode | FragmentDefinitionNode {
139 checkDocument(queryDoc);
140
141 let fragmentDefinition;
142
143 for (let definition of queryDoc.definitions) {
144 if (definition.kind === 'OperationDefinition') {
145 const operation = (definition as OperationDefinitionNode).operation;
146 if (
147 operation === 'query' ||
148 operation === 'mutation' ||
149 operation === 'subscription'
150 ) {
151 return definition as OperationDefinitionNode;
152 }
153 }
154 if (definition.kind === 'FragmentDefinition' && !fragmentDefinition) {
155 // we do this because we want to allow multiple fragment definitions
156 // to precede an operation definition.
157 fragmentDefinition = definition as FragmentDefinitionNode;
158 }
159 }
160
161 if (fragmentDefinition) {
162 return fragmentDefinition;
163 }
164
165 throw new InvariantError(
166 'Expected a parsed GraphQL query with a query, mutation, subscription, or a fragment.',
167 );
168}
169
170/**
171 * This is an interface that describes a map from fragment names to fragment definitions.
172 */
173export interface FragmentMap {
174 [fragmentName: string]: FragmentDefinitionNode;
175}
176
177// Utility function that takes a list of fragment definitions and makes a hash out of them
178// that maps the name of the fragment to the fragment definition.
179export function createFragmentMap(
180 fragments: FragmentDefinitionNode[] = [],
181): FragmentMap {
182 const symTable: FragmentMap = {};
183 fragments.forEach(fragment => {
184 symTable[fragment.name.value] = fragment;
185 });
186
187 return symTable;
188}
189
190export function getDefaultValues(
191 definition: OperationDefinitionNode | undefined,
192): { [key: string]: JsonValue } {
193 if (
194 definition &&
195 definition.variableDefinitions &&
196 definition.variableDefinitions.length
197 ) {
198 const defaultValues = definition.variableDefinitions
199 .filter(({ defaultValue }) => defaultValue)
200 .map(
201 ({ variable, defaultValue }): { [key: string]: JsonValue } => {
202 const defaultValueObj: { [key: string]: JsonValue } = {};
203 valueToObjectRepresentation(
204 defaultValueObj,
205 variable.name,
206 defaultValue as ValueNode,
207 );
208
209 return defaultValueObj;
210 },
211 );
212
213 return assign({}, ...defaultValues);
214 }
215
216 return {};
217}
218
219/**
220 * Returns the names of all variables declared by the operation.
221 */
222export function variablesInOperation(
223 operation: OperationDefinitionNode,
224): Set<string> {
225 const names = new Set<string>();
226 if (operation.variableDefinitions) {
227 for (const definition of operation.variableDefinitions) {
228 names.add(definition.variable.name.value);
229 }
230 }
231
232 return names;
233}