UNPKG

11.9 kBJavaScriptView Raw
1import { getNamedType, isEnumType, isInterfaceType, isListType, isNonNullType, isObjectType, isScalarType, isUnionType, Kind, } from 'graphql';
2import { getDefinedRootType, getRootTypeNames } from './rootTypes.js';
3let operationVariables = [];
4let fieldTypeMap = new Map();
5function addOperationVariable(variable) {
6 operationVariables.push(variable);
7}
8function resetOperationVariables() {
9 operationVariables = [];
10}
11function resetFieldMap() {
12 fieldTypeMap = new Map();
13}
14export function buildOperationNodeForField({ schema, kind, field, models, ignore = [], depthLimit, circularReferenceDepth, argNames, selectedFields = true, }) {
15 resetOperationVariables();
16 resetFieldMap();
17 const rootTypeNames = getRootTypeNames(schema);
18 const operationNode = buildOperationAndCollectVariables({
19 schema,
20 fieldName: field,
21 kind,
22 models: models || [],
23 ignore,
24 depthLimit: depthLimit || Infinity,
25 circularReferenceDepth: circularReferenceDepth || 1,
26 argNames,
27 selectedFields,
28 rootTypeNames,
29 });
30 // attach variables
31 operationNode.variableDefinitions = [...operationVariables];
32 resetOperationVariables();
33 resetFieldMap();
34 return operationNode;
35}
36function buildOperationAndCollectVariables({ schema, fieldName, kind, models, ignore, depthLimit, circularReferenceDepth, argNames, selectedFields, rootTypeNames, }) {
37 const type = getDefinedRootType(schema, kind);
38 const field = type.getFields()[fieldName];
39 const operationName = `${fieldName}_${kind}`;
40 if (field.args) {
41 for (const arg of field.args) {
42 const argName = arg.name;
43 if (!argNames || argNames.includes(argName)) {
44 addOperationVariable(resolveVariable(arg, argName));
45 }
46 }
47 }
48 return {
49 kind: Kind.OPERATION_DEFINITION,
50 operation: kind,
51 name: {
52 kind: Kind.NAME,
53 value: operationName,
54 },
55 variableDefinitions: [],
56 selectionSet: {
57 kind: Kind.SELECTION_SET,
58 selections: [
59 resolveField({
60 type,
61 field,
62 models,
63 firstCall: true,
64 path: [],
65 ancestors: [],
66 ignore,
67 depthLimit,
68 circularReferenceDepth,
69 schema,
70 depth: 0,
71 argNames,
72 selectedFields,
73 rootTypeNames,
74 }),
75 ],
76 },
77 };
78}
79function resolveSelectionSet({ parent, type, models, firstCall, path, ancestors, ignore, depthLimit, circularReferenceDepth, schema, depth, argNames, selectedFields, rootTypeNames, }) {
80 if (typeof selectedFields === 'boolean' && depth > depthLimit) {
81 return;
82 }
83 if (isUnionType(type)) {
84 const types = type.getTypes();
85 return {
86 kind: Kind.SELECTION_SET,
87 selections: types
88 .filter(t => !hasCircularRef([...ancestors, t], {
89 depth: circularReferenceDepth,
90 }))
91 .map(t => {
92 return {
93 kind: Kind.INLINE_FRAGMENT,
94 typeCondition: {
95 kind: Kind.NAMED_TYPE,
96 name: {
97 kind: Kind.NAME,
98 value: t.name,
99 },
100 },
101 selectionSet: resolveSelectionSet({
102 parent: type,
103 type: t,
104 models,
105 path,
106 ancestors,
107 ignore,
108 depthLimit,
109 circularReferenceDepth,
110 schema,
111 depth,
112 argNames,
113 selectedFields,
114 rootTypeNames,
115 }),
116 };
117 })
118 .filter(fragmentNode => fragmentNode?.selectionSet?.selections?.length > 0),
119 };
120 }
121 if (isInterfaceType(type)) {
122 const types = Object.values(schema.getTypeMap()).filter((t) => isObjectType(t) && t.getInterfaces().includes(type));
123 return {
124 kind: Kind.SELECTION_SET,
125 selections: types
126 .filter(t => !hasCircularRef([...ancestors, t], {
127 depth: circularReferenceDepth,
128 }))
129 .map(t => {
130 return {
131 kind: Kind.INLINE_FRAGMENT,
132 typeCondition: {
133 kind: Kind.NAMED_TYPE,
134 name: {
135 kind: Kind.NAME,
136 value: t.name,
137 },
138 },
139 selectionSet: resolveSelectionSet({
140 parent: type,
141 type: t,
142 models,
143 path,
144 ancestors,
145 ignore,
146 depthLimit,
147 circularReferenceDepth,
148 schema,
149 depth,
150 argNames,
151 selectedFields,
152 rootTypeNames,
153 }),
154 };
155 })
156 .filter(fragmentNode => fragmentNode?.selectionSet?.selections?.length > 0),
157 };
158 }
159 if (isObjectType(type) && !rootTypeNames.has(type.name)) {
160 const isIgnored = ignore.includes(type.name) || ignore.includes(`${parent.name}.${path[path.length - 1]}`);
161 const isModel = models.includes(type.name);
162 if (!firstCall && isModel && !isIgnored) {
163 return {
164 kind: Kind.SELECTION_SET,
165 selections: [
166 {
167 kind: Kind.FIELD,
168 name: {
169 kind: Kind.NAME,
170 value: 'id',
171 },
172 },
173 ],
174 };
175 }
176 const fields = type.getFields();
177 return {
178 kind: Kind.SELECTION_SET,
179 selections: Object.keys(fields)
180 .filter(fieldName => {
181 return !hasCircularRef([...ancestors, getNamedType(fields[fieldName].type)], {
182 depth: circularReferenceDepth,
183 });
184 })
185 .map(fieldName => {
186 const selectedSubFields = typeof selectedFields === 'object' ? selectedFields[fieldName] : true;
187 if (selectedSubFields) {
188 return resolveField({
189 type,
190 field: fields[fieldName],
191 models,
192 path: [...path, fieldName],
193 ancestors,
194 ignore,
195 depthLimit,
196 circularReferenceDepth,
197 schema,
198 depth,
199 argNames,
200 selectedFields: selectedSubFields,
201 rootTypeNames,
202 });
203 }
204 return null;
205 })
206 .filter((f) => {
207 if (f == null) {
208 return false;
209 }
210 else if ('selectionSet' in f) {
211 return !!f.selectionSet?.selections?.length;
212 }
213 return true;
214 }),
215 };
216 }
217}
218function resolveVariable(arg, name) {
219 function resolveVariableType(type) {
220 if (isListType(type)) {
221 return {
222 kind: Kind.LIST_TYPE,
223 type: resolveVariableType(type.ofType),
224 };
225 }
226 if (isNonNullType(type)) {
227 return {
228 kind: Kind.NON_NULL_TYPE,
229 // for v16 compatibility
230 type: resolveVariableType(type.ofType),
231 };
232 }
233 return {
234 kind: Kind.NAMED_TYPE,
235 name: {
236 kind: Kind.NAME,
237 value: type.name,
238 },
239 };
240 }
241 return {
242 kind: Kind.VARIABLE_DEFINITION,
243 variable: {
244 kind: Kind.VARIABLE,
245 name: {
246 kind: Kind.NAME,
247 value: name || arg.name,
248 },
249 },
250 type: resolveVariableType(arg.type),
251 };
252}
253function getArgumentName(name, path) {
254 return [...path, name].join('_');
255}
256function resolveField({ type, field, models, firstCall, path, ancestors, ignore, depthLimit, circularReferenceDepth, schema, depth, argNames, selectedFields, rootTypeNames, }) {
257 const namedType = getNamedType(field.type);
258 let args = [];
259 let removeField = false;
260 if (field.args && field.args.length) {
261 args = field.args
262 .map(arg => {
263 const argumentName = getArgumentName(arg.name, path);
264 if (argNames && !argNames.includes(argumentName)) {
265 if (isNonNullType(arg.type)) {
266 removeField = true;
267 }
268 return null;
269 }
270 if (!firstCall) {
271 addOperationVariable(resolveVariable(arg, argumentName));
272 }
273 return {
274 kind: Kind.ARGUMENT,
275 name: {
276 kind: Kind.NAME,
277 value: arg.name,
278 },
279 value: {
280 kind: Kind.VARIABLE,
281 name: {
282 kind: Kind.NAME,
283 value: getArgumentName(arg.name, path),
284 },
285 },
286 };
287 })
288 .filter(Boolean);
289 }
290 if (removeField) {
291 return null;
292 }
293 const fieldPath = [...path, field.name];
294 const fieldPathStr = fieldPath.join('.');
295 let fieldName = field.name;
296 if (fieldTypeMap.has(fieldPathStr) && fieldTypeMap.get(fieldPathStr) !== field.type.toString()) {
297 fieldName += field.type
298 .toString()
299 .replace('!', 'NonNull')
300 .replace('[', 'List')
301 .replace(']', '');
302 }
303 fieldTypeMap.set(fieldPathStr, field.type.toString());
304 if (!isScalarType(namedType) && !isEnumType(namedType)) {
305 return {
306 kind: Kind.FIELD,
307 name: {
308 kind: Kind.NAME,
309 value: field.name,
310 },
311 ...(fieldName !== field.name && { alias: { kind: Kind.NAME, value: fieldName } }),
312 selectionSet: resolveSelectionSet({
313 parent: type,
314 type: namedType,
315 models,
316 firstCall,
317 path: fieldPath,
318 ancestors: [...ancestors, type],
319 ignore,
320 depthLimit,
321 circularReferenceDepth,
322 schema,
323 depth: depth + 1,
324 argNames,
325 selectedFields,
326 rootTypeNames,
327 }) || undefined,
328 arguments: args,
329 };
330 }
331 return {
332 kind: Kind.FIELD,
333 name: {
334 kind: Kind.NAME,
335 value: field.name,
336 },
337 ...(fieldName !== field.name && { alias: { kind: Kind.NAME, value: fieldName } }),
338 arguments: args,
339 };
340}
341function hasCircularRef(types, config = {
342 depth: 1,
343}) {
344 const type = types[types.length - 1];
345 if (isScalarType(type)) {
346 return false;
347 }
348 const size = types.filter(t => t.name === type.name).length;
349 return size > config.depth;
350}