UNPKG

6.53 kBPlain TextView Raw
1import { CompilerContext } from "apollo-codegen-core/lib/compiler";
2import {
3 InlineSelection,
4 Typename,
5 Maybe,
6 List as ListType
7} from "./intermediates";
8import {
9 Fragments,
10 ExtendedSelection,
11 ExtendedFieldType,
12 ExtendedFields,
13 ByTypename,
14 ExtendedField,
15 extendedSelectionsAreEqual
16} from "./extendedIntermediates";
17import { Map, List, Set } from "immutable";
18
19type NormalizableSelections = ByTypename<List<ExtendedFields>>;
20
21const normalizableSelectionsFromExtendedFieldType = (
22 type: ExtendedFieldType
23): NormalizableSelections => {
24 switch (type.kind) {
25 case "InlineSelection":
26 return NormalizableSelections(type);
27 case "Maybe":
28 return normalizableSelectionsFromExtendedFieldType(type.ofType);
29 case "List":
30 return normalizableSelectionsFromExtendedFieldType(type.ofType);
31 default:
32 return Map();
33 }
34};
35
36const selectTypename = (__typename: string) => (field: ExtendedField) =>
37 field.type.kind == "Typename"
38 ? { type: Typename(Set([__typename])), optional: field.optional }
39 : field;
40
41const NormalizableSelections = (
42 selection: ExtendedSelection
43): NormalizableSelections =>
44 selection.fields.reduce(
45 (normalizedSelections, fields, __typename) =>
46 fields.reduce(
47 (normalizedSelections, field) =>
48 normalizedSelections.mergeDeep(
49 normalizableSelectionsFromExtendedFieldType(field.type)
50 ),
51 isNormalizable(fields)
52 ? normalizedSelections.mergeDeep(
53 Map({
54 [__typename]: List([fields.map(selectTypename(__typename))])
55 })
56 )
57 : normalizedSelections
58 ),
59 Map()
60 );
61
62const normalizeFieldType = (type: ExtendedFieldType): ExtendedFieldType => {
63 switch (type.kind) {
64 case "InlineSelection":
65 return normalizeSelection(type);
66 case "Maybe":
67 return Maybe(normalizeFieldType(type.ofType));
68 case "List":
69 return ListType(normalizeFieldType(type.ofType));
70 default:
71 return type;
72 }
73};
74
75const normalizeField = ({ type, optional }: ExtendedField): ExtendedField => ({
76 type: normalizeFieldType(type),
77 optional
78});
79
80const normalizeSelection = (
81 selection: ExtendedSelection
82): ExtendedSelection => ({
83 kind: "InlineSelection",
84 fields: selection.fields.map(
85 fields =>
86 isNormalizable(fields)
87 ? fields.filter(
88 (_, fieldName) => fieldName == "__typename" || fieldName == "id"
89 )
90 : fields.map(normalizeField)
91 )
92});
93
94const returnIfEqual = (
95 __typename: string,
96 fieldName: string,
97 lhs: ExtendedFieldType,
98 rhs: ExtendedFieldType
99): ExtendedFieldType => {
100 const differentKindsError = Error(
101 `${lhs.kind} is not the same as ${rhs.kind}`
102 );
103 switch (lhs.kind) {
104 case "Typename":
105 if (rhs.kind != "Typename") {
106 throw differentKindsError;
107 }
108 if (!lhs.possibleTypes.equals(rhs.possibleTypes)) {
109 throw Error(
110 `${lhs.possibleTypes} are not the same as ${rhs.possibleTypes}`
111 );
112 }
113 return rhs;
114 case "Enum":
115 if (rhs.kind != "Enum") {
116 throw differentKindsError;
117 }
118 if (lhs.name != rhs.name) {
119 throw Error(`${lhs.name} is different than ${rhs.name}`);
120 }
121 if (lhs.values != rhs.values) {
122 throw Error(
123 `Possible values ${lhs.values} are not the same as ${rhs.values}`
124 );
125 }
126 return rhs;
127 case "Scalar":
128 if (rhs.kind != "Scalar") {
129 throw differentKindsError;
130 }
131 if (lhs.name != rhs.name) {
132 throw Error(`Types ${lhs.name} and ${rhs.name} are not the same`);
133 }
134 return rhs;
135 case "InlineSelection":
136 if (rhs.kind != "InlineSelection") {
137 throw differentKindsError;
138 }
139 if (!extendedSelectionsAreEqual(lhs, rhs)) {
140 throw Error(
141 `${__typename} can't be normalized because it has conflicting definitions for "${fieldName}" that could lead to runtime bugs. Either alias the field name or make the selections the same.`
142 );
143 }
144 return rhs;
145 case "List":
146 if (rhs.kind != "List") {
147 throw differentKindsError;
148 }
149 return ListType(
150 returnIfEqual(__typename, fieldName, lhs.ofType, rhs.ofType)
151 );
152 case "Maybe":
153 if (rhs.kind != "Maybe") {
154 throw differentKindsError;
155 }
156 return Maybe(
157 returnIfEqual(__typename, fieldName, lhs.ofType, rhs.ofType)
158 );
159 }
160};
161
162const mergeExtendedSelections = (
163 lhs: ExtendedSelection,
164 rhs: ExtendedSelection
165): ExtendedSelection => {
166 return {
167 kind: "InlineSelection",
168 fields: lhs.fields.mergeWith(
169 (lhs, rhs, __typename) => mergeFields(__typename)(lhs, rhs),
170 rhs.fields
171 )
172 };
173};
174
175const isNormalizable = (fields: ExtendedFields) =>
176 fields.has("__typename") && fields.has("id");
177
178const mergeField = (__typename: string) => (
179 lhs: ExtendedField,
180 rhs: ExtendedField,
181 fieldName: string
182): ExtendedField => ({
183 type:
184 lhs.type.kind == "InlineSelection" &&
185 rhs.type.kind == "InlineSelection" &&
186 !lhs.type.fields.some(isNormalizable) &&
187 !rhs.type.fields.some(isNormalizable)
188 ? mergeExtendedSelections(lhs.type, rhs.type)
189 : returnIfEqual(__typename, fieldName, lhs.type, rhs.type),
190 optional: lhs.optional || rhs.optional
191});
192
193const mergeFields = (__typename: string) => (
194 lhs: ExtendedFields,
195 rhs: ExtendedFields
196): ExtendedFields => {
197 const intersection = Set(lhs.keySeq()).intersect(Set(rhs.keySeq()));
198 return lhs
199 .mergeWith(mergeField(__typename), rhs)
200 .map(
201 (field, fieldName) =>
202 !intersection.contains(fieldName)
203 ? { type: field.type, optional: true }
204 : field
205 );
206};
207
208export const normalizedTypes = (context: CompilerContext) => {
209 const fragments: Fragments = Object.values(context.fragments).reduce(
210 (fragments, fragment) => ({
211 ...fragments,
212 [fragment.fragmentName]: InlineSelection(fragment.selectionSet)
213 }),
214 {}
215 );
216 const extendedSelections = Object.values(context.operations).map(operation =>
217 ExtendedSelection(InlineSelection(operation.selectionSet), fragments)
218 );
219 const normalizableSelections = extendedSelections.reduce(
220 (normalizedSelections, extendedSelection) =>
221 normalizedSelections.mergeDeep(NormalizableSelections(extendedSelection)),
222 Map() as NormalizableSelections
223 );
224 return normalizableSelections.map((fields, __typename) =>
225 fields.reduce(mergeFields(__typename)).map(normalizeField)
226 );
227};