UNPKG

10.7 kBJavaScriptView Raw
1import { getNullableType, isAbstractType, isListType, isObjectType, Kind, SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef, } from 'graphql';
2import { collectFields, collectSubFields } from './collectFields.js';
3import { getOperationASTFromRequest } from './getOperationASTFromRequest.js';
4export function visitData(data, enter, leave) {
5 if (Array.isArray(data)) {
6 return data.map(value => visitData(value, enter, leave));
7 }
8 else if (typeof data === 'object') {
9 const newData = enter != null ? enter(data) : data;
10 if (newData != null) {
11 for (const key in newData) {
12 const value = newData[key];
13 Object.defineProperty(newData, key, {
14 value: visitData(value, enter, leave),
15 });
16 }
17 }
18 return leave != null ? leave(newData) : newData;
19 }
20 return data;
21}
22export function visitErrors(errors, visitor) {
23 return errors.map(error => visitor(error));
24}
25export function visitResult(result, request, schema, resultVisitorMap, errorVisitorMap) {
26 const fragments = request.document.definitions.reduce((acc, def) => {
27 if (def.kind === Kind.FRAGMENT_DEFINITION) {
28 acc[def.name.value] = def;
29 }
30 return acc;
31 }, {});
32 const variableValues = request.variables || {};
33 const errorInfo = {
34 segmentInfoMap: new Map(),
35 unpathedErrors: new Set(),
36 };
37 const data = result.data;
38 const errors = result.errors;
39 const visitingErrors = errors != null && errorVisitorMap != null;
40 const operationDocumentNode = getOperationASTFromRequest(request);
41 if (data != null && operationDocumentNode != null) {
42 result.data = visitRoot(data, operationDocumentNode, schema, fragments, variableValues, resultVisitorMap, visitingErrors ? errors : undefined, errorInfo);
43 }
44 if (errors != null && errorVisitorMap) {
45 result.errors = visitErrorsByType(errors, errorVisitorMap, errorInfo);
46 }
47 return result;
48}
49function visitErrorsByType(errors, errorVisitorMap, errorInfo) {
50 const segmentInfoMap = errorInfo.segmentInfoMap;
51 const unpathedErrors = errorInfo.unpathedErrors;
52 const unpathedErrorVisitor = errorVisitorMap['__unpathed'];
53 return errors.map(originalError => {
54 const pathSegmentsInfo = segmentInfoMap.get(originalError);
55 const newError = pathSegmentsInfo == null
56 ? originalError
57 : pathSegmentsInfo.reduceRight((acc, segmentInfo) => {
58 const typeName = segmentInfo.type.name;
59 const typeVisitorMap = errorVisitorMap[typeName];
60 if (typeVisitorMap == null) {
61 return acc;
62 }
63 const errorVisitor = typeVisitorMap[segmentInfo.fieldName];
64 return errorVisitor == null ? acc : errorVisitor(acc, segmentInfo.pathIndex);
65 }, originalError);
66 if (unpathedErrorVisitor && unpathedErrors.has(originalError)) {
67 return unpathedErrorVisitor(newError);
68 }
69 return newError;
70 });
71}
72function getOperationRootType(schema, operationDef) {
73 switch (operationDef.operation) {
74 case 'query':
75 return schema.getQueryType();
76 case 'mutation':
77 return schema.getMutationType();
78 case 'subscription':
79 return schema.getSubscriptionType();
80 }
81}
82function visitRoot(root, operation, schema, fragments, variableValues, resultVisitorMap, errors, errorInfo) {
83 const operationRootType = getOperationRootType(schema, operation);
84 const { fields: collectedFields } = collectFields(schema, fragments, variableValues, operationRootType, operation.selectionSet);
85 return visitObjectValue(root, operationRootType, collectedFields, schema, fragments, variableValues, resultVisitorMap, 0, errors, errorInfo);
86}
87function visitObjectValue(object, type, fieldNodeMap, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo) {
88 const fieldMap = type.getFields();
89 const typeVisitorMap = resultVisitorMap?.[type.name];
90 const enterObject = typeVisitorMap?.__enter;
91 const newObject = enterObject != null ? enterObject(object) : object;
92 let sortedErrors;
93 let errorMap = null;
94 if (errors != null) {
95 sortedErrors = sortErrorsByPathSegment(errors, pathIndex);
96 errorMap = sortedErrors.errorMap;
97 for (const error of sortedErrors.unpathedErrors) {
98 errorInfo.unpathedErrors.add(error);
99 }
100 }
101 for (const [responseKey, subFieldNodes] of fieldNodeMap) {
102 const fieldName = subFieldNodes[0].name.value;
103 let fieldType = fieldMap[fieldName]?.type;
104 if (fieldType == null) {
105 switch (fieldName) {
106 case '__typename':
107 fieldType = TypeNameMetaFieldDef.type;
108 break;
109 case '__schema':
110 fieldType = SchemaMetaFieldDef.type;
111 break;
112 case '__type':
113 fieldType = TypeMetaFieldDef.type;
114 break;
115 }
116 }
117 const newPathIndex = pathIndex + 1;
118 let fieldErrors;
119 if (errorMap) {
120 fieldErrors = errorMap[responseKey];
121 if (fieldErrors != null) {
122 delete errorMap[responseKey];
123 }
124 addPathSegmentInfo(type, fieldName, newPathIndex, fieldErrors, errorInfo);
125 }
126 const newValue = visitFieldValue(object[responseKey], fieldType, subFieldNodes, schema, fragments, variableValues, resultVisitorMap, newPathIndex, fieldErrors, errorInfo);
127 updateObject(newObject, responseKey, newValue, typeVisitorMap, fieldName);
128 }
129 const oldTypename = newObject.__typename;
130 if (oldTypename != null) {
131 updateObject(newObject, '__typename', oldTypename, typeVisitorMap, '__typename');
132 }
133 if (errorMap) {
134 for (const errorsKey in errorMap) {
135 const errors = errorMap[errorsKey];
136 for (const error of errors) {
137 errorInfo.unpathedErrors.add(error);
138 }
139 }
140 }
141 const leaveObject = typeVisitorMap?.__leave;
142 return leaveObject != null ? leaveObject(newObject) : newObject;
143}
144function updateObject(object, responseKey, newValue, typeVisitorMap, fieldName) {
145 if (typeVisitorMap == null) {
146 object[responseKey] = newValue;
147 return;
148 }
149 const fieldVisitor = typeVisitorMap[fieldName];
150 if (fieldVisitor == null) {
151 object[responseKey] = newValue;
152 return;
153 }
154 const visitedValue = fieldVisitor(newValue);
155 if (visitedValue === undefined) {
156 delete object[responseKey];
157 return;
158 }
159 object[responseKey] = visitedValue;
160}
161function visitListValue(list, returnType, fieldNodes, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo) {
162 return list.map(listMember => visitFieldValue(listMember, returnType, fieldNodes, schema, fragments, variableValues, resultVisitorMap, pathIndex + 1, errors, errorInfo));
163}
164function visitFieldValue(value, returnType, fieldNodes, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors = [], errorInfo) {
165 if (value == null) {
166 return value;
167 }
168 const nullableType = getNullableType(returnType);
169 if (isListType(nullableType)) {
170 return visitListValue(value, nullableType.ofType, fieldNodes, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo);
171 }
172 else if (isAbstractType(nullableType)) {
173 const finalType = schema.getType(value.__typename);
174 let { fields: collectedFields, patches } = collectSubFields(schema, fragments, variableValues, finalType, fieldNodes);
175 if (patches.length) {
176 collectedFields = new Map(collectedFields);
177 for (const patch of patches) {
178 for (const [responseKey, fields] of patch.fields) {
179 const existingFields = collectedFields.get(responseKey);
180 if (existingFields) {
181 existingFields.push(...fields);
182 }
183 else {
184 collectedFields.set(responseKey, fields);
185 }
186 }
187 }
188 }
189 return visitObjectValue(value, finalType, collectedFields, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo);
190 }
191 else if (isObjectType(nullableType)) {
192 let { fields: collectedFields, patches } = collectSubFields(schema, fragments, variableValues, nullableType, fieldNodes);
193 if (patches.length) {
194 collectedFields = new Map(collectedFields);
195 for (const patch of patches) {
196 for (const [responseKey, fields] of patch.fields) {
197 const existingFields = collectedFields.get(responseKey);
198 if (existingFields) {
199 existingFields.push(...fields);
200 }
201 else {
202 collectedFields.set(responseKey, fields);
203 }
204 }
205 }
206 }
207 return visitObjectValue(value, nullableType, collectedFields, schema, fragments, variableValues, resultVisitorMap, pathIndex, errors, errorInfo);
208 }
209 const typeVisitorMap = resultVisitorMap?.[nullableType.name];
210 if (typeVisitorMap == null) {
211 return value;
212 }
213 const visitedValue = typeVisitorMap(value);
214 return visitedValue === undefined ? value : visitedValue;
215}
216function sortErrorsByPathSegment(errors, pathIndex) {
217 const errorMap = Object.create(null);
218 const unpathedErrors = new Set();
219 for (const error of errors) {
220 const pathSegment = error.path?.[pathIndex];
221 if (pathSegment == null) {
222 unpathedErrors.add(error);
223 continue;
224 }
225 if (pathSegment in errorMap) {
226 errorMap[pathSegment].push(error);
227 }
228 else {
229 errorMap[pathSegment] = [error];
230 }
231 }
232 return {
233 errorMap,
234 unpathedErrors,
235 };
236}
237function addPathSegmentInfo(type, fieldName, pathIndex, errors = [], errorInfo) {
238 for (const error of errors) {
239 const segmentInfo = {
240 type,
241 fieldName,
242 pathIndex,
243 };
244 const pathSegmentsInfo = errorInfo.segmentInfoMap.get(error);
245 if (pathSegmentsInfo == null) {
246 errorInfo.segmentInfoMap.set(error, [segmentInfo]);
247 }
248 else {
249 pathSegmentsInfo.push(segmentInfo);
250 }
251 }
252}