1 | import { isObjectType, getNamedType, isUnionType, isNonNullType, isScalarType, isListType, isInterfaceType, isEnumType, Kind, } from 'graphql';
|
2 | import { getDefinedRootType, getRootTypeNames } from './rootTypes.js';
|
3 | let operationVariables = [];
|
4 | let fieldTypeMap = new Map();
|
5 | function addOperationVariable(variable) {
|
6 | operationVariables.push(variable);
|
7 | }
|
8 | function resetOperationVariables() {
|
9 | operationVariables = [];
|
10 | }
|
11 | function resetFieldMap() {
|
12 | fieldTypeMap = new Map();
|
13 | }
|
14 | export 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 |
|
31 | operationNode.variableDefinitions = [...operationVariables];
|
32 | resetOperationVariables();
|
33 | resetFieldMap();
|
34 | return operationNode;
|
35 | }
|
36 | function 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 | }
|
79 | function 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 => { var _a, _b; return ((_b = (_a = fragmentNode === null || fragmentNode === void 0 ? void 0 : fragmentNode.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) === null || _b === void 0 ? void 0 : _b.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 => { var _a, _b; return ((_b = (_a = fragmentNode === null || fragmentNode === void 0 ? void 0 : fragmentNode.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) === null || _b === void 0 ? void 0 : _b.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 | var _a, _b;
|
208 | if (f == null) {
|
209 | return false;
|
210 | }
|
211 | else if ('selectionSet' in f) {
|
212 | return !!((_b = (_a = f.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) === null || _b === void 0 ? void 0 : _b.length);
|
213 | }
|
214 | return true;
|
215 | }),
|
216 | };
|
217 | }
|
218 | }
|
219 | function resolveVariable(arg, name) {
|
220 | function resolveVariableType(type) {
|
221 | if (isListType(type)) {
|
222 | return {
|
223 | kind: Kind.LIST_TYPE,
|
224 | type: resolveVariableType(type.ofType),
|
225 | };
|
226 | }
|
227 | if (isNonNullType(type)) {
|
228 | return {
|
229 | kind: Kind.NON_NULL_TYPE,
|
230 |
|
231 | type: resolveVariableType(type.ofType),
|
232 | };
|
233 | }
|
234 | return {
|
235 | kind: Kind.NAMED_TYPE,
|
236 | name: {
|
237 | kind: Kind.NAME,
|
238 | value: type.name,
|
239 | },
|
240 | };
|
241 | }
|
242 | return {
|
243 | kind: Kind.VARIABLE_DEFINITION,
|
244 | variable: {
|
245 | kind: Kind.VARIABLE,
|
246 | name: {
|
247 | kind: Kind.NAME,
|
248 | value: name || arg.name,
|
249 | },
|
250 | },
|
251 | type: resolveVariableType(arg.type),
|
252 | };
|
253 | }
|
254 | function getArgumentName(name, path) {
|
255 | return [...path, name].join('_');
|
256 | }
|
257 | function resolveField({ type, field, models, firstCall, path, ancestors, ignore, depthLimit, circularReferenceDepth, schema, depth, argNames, selectedFields, rootTypeNames, }) {
|
258 | const namedType = getNamedType(field.type);
|
259 | let args = [];
|
260 | let removeField = false;
|
261 | if (field.args && field.args.length) {
|
262 | args = field.args
|
263 | .map(arg => {
|
264 | const argumentName = getArgumentName(arg.name, path);
|
265 | if (argNames && !argNames.includes(argumentName)) {
|
266 | if (isNonNullType(arg.type)) {
|
267 | removeField = true;
|
268 | }
|
269 | return null;
|
270 | }
|
271 | if (!firstCall) {
|
272 | addOperationVariable(resolveVariable(arg, argumentName));
|
273 | }
|
274 | return {
|
275 | kind: Kind.ARGUMENT,
|
276 | name: {
|
277 | kind: Kind.NAME,
|
278 | value: arg.name,
|
279 | },
|
280 | value: {
|
281 | kind: Kind.VARIABLE,
|
282 | name: {
|
283 | kind: Kind.NAME,
|
284 | value: getArgumentName(arg.name, path),
|
285 | },
|
286 | },
|
287 | };
|
288 | })
|
289 | .filter(Boolean);
|
290 | }
|
291 | if (removeField) {
|
292 | return null;
|
293 | }
|
294 | const fieldPath = [...path, field.name];
|
295 | const fieldPathStr = fieldPath.join('.');
|
296 | let fieldName = field.name;
|
297 | if (fieldTypeMap.has(fieldPathStr) && fieldTypeMap.get(fieldPathStr) !== field.type.toString()) {
|
298 | fieldName += field.type.toString().replace('!', 'NonNull');
|
299 | }
|
300 | fieldTypeMap.set(fieldPathStr, field.type.toString());
|
301 | if (!isScalarType(namedType) && !isEnumType(namedType)) {
|
302 | return {
|
303 | kind: Kind.FIELD,
|
304 | name: {
|
305 | kind: Kind.NAME,
|
306 | value: field.name,
|
307 | },
|
308 | ...(fieldName !== field.name && { alias: { kind: Kind.NAME, value: fieldName } }),
|
309 | selectionSet: resolveSelectionSet({
|
310 | parent: type,
|
311 | type: namedType,
|
312 | models,
|
313 | firstCall,
|
314 | path: fieldPath,
|
315 | ancestors: [...ancestors, type],
|
316 | ignore,
|
317 | depthLimit,
|
318 | circularReferenceDepth,
|
319 | schema,
|
320 | depth: depth + 1,
|
321 | argNames,
|
322 | selectedFields,
|
323 | rootTypeNames,
|
324 | }) || undefined,
|
325 | arguments: args,
|
326 | };
|
327 | }
|
328 | return {
|
329 | kind: Kind.FIELD,
|
330 | name: {
|
331 | kind: Kind.NAME,
|
332 | value: field.name,
|
333 | },
|
334 | ...(fieldName !== field.name && { alias: { kind: Kind.NAME, value: fieldName } }),
|
335 | arguments: args,
|
336 | };
|
337 | }
|
338 | function hasCircularRef(types, config = {
|
339 | depth: 1,
|
340 | }) {
|
341 | const type = types[types.length - 1];
|
342 | if (isScalarType(type)) {
|
343 | return false;
|
344 | }
|
345 | const size = types.filter(t => t.name === type.name).length;
|
346 | return size > config.depth;
|
347 | }
|