1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const path = require("path");
|
4 | const t = require("@babel/types");
|
5 | const common_tags_1 = require("common-tags");
|
6 | const graphql_1 = require("graphql");
|
7 | const typeCase_1 = require("apollo-codegen-core/lib/compiler/visitors/typeCase");
|
8 | const collectAndMergeFields_1 = require("apollo-codegen-core/lib/compiler/visitors/collectAndMergeFields");
|
9 | const language_1 = require("./language");
|
10 | const printer_1 = require("./printer");
|
11 | const graphql_2 = require("graphql");
|
12 | const array_1 = require("apollo-codegen-core/lib/utilities/array");
|
13 | class TypescriptGeneratedFile {
|
14 | constructor(fileContents) {
|
15 | this.fileContents = fileContents;
|
16 | }
|
17 | get output() {
|
18 | return this.fileContents;
|
19 | }
|
20 | }
|
21 | function printEnumsAndInputObjects(generator, typesUsed) {
|
22 | generator.printer.enqueue(common_tags_1.stripIndent `
|
23 | //==============================================================
|
24 | // START Enums and Input Objects
|
25 | //==============================================================
|
26 | `);
|
27 | typesUsed
|
28 | .filter(type => type instanceof graphql_1.GraphQLEnumType)
|
29 | .sort()
|
30 | .forEach(enumType => {
|
31 | generator.typeAliasForEnumType(enumType);
|
32 | });
|
33 | typesUsed
|
34 | .filter(type => type instanceof graphql_1.GraphQLInputObjectType)
|
35 | .sort()
|
36 | .forEach(inputObjectType => {
|
37 | generator.typeAliasForInputObjectType(inputObjectType);
|
38 | });
|
39 | generator.printer.enqueue(common_tags_1.stripIndent `
|
40 | //==============================================================
|
41 | // END Enums and Input Objects
|
42 | //==============================================================
|
43 | `);
|
44 | }
|
45 | function printGlobalImport(generator, typesUsed, outputPath, globalSourcePath) {
|
46 | if (typesUsed.length > 0) {
|
47 | const relative = path.relative(path.dirname(outputPath), path.join(path.dirname(globalSourcePath), path.basename(globalSourcePath, ".ts")));
|
48 | generator.printer.enqueue(generator.import(typesUsed, "./" + relative));
|
49 | }
|
50 | }
|
51 | function generateSource(context) {
|
52 | const generator = new TypescriptAPIGenerator(context);
|
53 | const generatedFiles = [];
|
54 | Object.values(context.operations).forEach(operation => {
|
55 | generator.fileHeader();
|
56 | generator.interfacesForOperation(operation);
|
57 | const output = generator.printer.printAndClear();
|
58 | generatedFiles.push({
|
59 | sourcePath: operation.filePath,
|
60 | fileName: `${operation.operationName}.ts`,
|
61 | content: new TypescriptGeneratedFile(output)
|
62 | });
|
63 | });
|
64 | Object.values(context.fragments).forEach(fragment => {
|
65 | generator.fileHeader();
|
66 | generator.interfacesForFragment(fragment);
|
67 | const output = generator.printer.printAndClear();
|
68 | generatedFiles.push({
|
69 | sourcePath: fragment.filePath,
|
70 | fileName: `${fragment.fragmentName}.ts`,
|
71 | content: new TypescriptGeneratedFile(output)
|
72 | });
|
73 | });
|
74 | generator.fileHeader();
|
75 | printEnumsAndInputObjects(generator, context.typesUsed);
|
76 | const common = generator.printer.printAndClear();
|
77 | return {
|
78 | generatedFiles,
|
79 | common
|
80 | };
|
81 | }
|
82 | exports.generateSource = generateSource;
|
83 | function generateLocalSource(context) {
|
84 | const generator = new TypescriptAPIGenerator(context);
|
85 | const operations = Object.values(context.operations).map(operation => ({
|
86 | sourcePath: operation.filePath,
|
87 | fileName: `${operation.operationName}.ts`,
|
88 | content: (options) => {
|
89 | generator.fileHeader();
|
90 | if (options && options.outputPath && options.globalSourcePath) {
|
91 | printGlobalImport(generator, generator.getGlobalTypesUsedForOperation(operation), options.outputPath, options.globalSourcePath);
|
92 | }
|
93 | generator.interfacesForOperation(operation);
|
94 | const output = generator.printer.printAndClear();
|
95 | return new TypescriptGeneratedFile(output);
|
96 | }
|
97 | }));
|
98 | const fragments = Object.values(context.fragments).map(fragment => ({
|
99 | sourcePath: fragment.filePath,
|
100 | fileName: `${fragment.fragmentName}.ts`,
|
101 | content: (options) => {
|
102 | generator.fileHeader();
|
103 | if (options && options.outputPath && options.globalSourcePath) {
|
104 | printGlobalImport(generator, generator.getGlobalTypesUsedForFragment(fragment), options.outputPath, options.globalSourcePath);
|
105 | }
|
106 | generator.interfacesForFragment(fragment);
|
107 | const output = generator.printer.printAndClear();
|
108 | return new TypescriptGeneratedFile(output);
|
109 | }
|
110 | }));
|
111 | return operations.concat(fragments);
|
112 | }
|
113 | exports.generateLocalSource = generateLocalSource;
|
114 | function generateGlobalSource(context) {
|
115 | const generator = new TypescriptAPIGenerator(context);
|
116 | generator.fileHeader();
|
117 | printEnumsAndInputObjects(generator, context.typesUsed);
|
118 | const output = generator.printer.printAndClear();
|
119 | return new TypescriptGeneratedFile(output);
|
120 | }
|
121 | exports.generateGlobalSource = generateGlobalSource;
|
122 | class TypescriptAPIGenerator extends language_1.default {
|
123 | constructor(context) {
|
124 | super(context.options);
|
125 | this.getGlobalTypesUsedForOperation = (doc) => {
|
126 | const typesUsed = doc.variables.reduce((acc, { type }) => {
|
127 | const t = this.getUnderlyingType(type);
|
128 | if (this.isGlobalType(t)) {
|
129 | return array_1.maybePush(acc, t);
|
130 | }
|
131 | return acc;
|
132 | }, []);
|
133 | return doc.selectionSet.selections.reduce(this.reduceSelection, typesUsed);
|
134 | };
|
135 | this.getGlobalTypesUsedForFragment = (doc) => {
|
136 | return doc.selectionSet.selections.reduce(this.reduceSelection, []);
|
137 | };
|
138 | this.reduceSelection = (acc, selection) => {
|
139 | if (selection.kind === "Field" || selection.kind === "TypeCondition") {
|
140 | const type = this.getUnderlyingType(selection.type);
|
141 | if (this.isGlobalType(type)) {
|
142 | acc = array_1.maybePush(acc, type);
|
143 | }
|
144 | }
|
145 | if (selection.selectionSet) {
|
146 | return selection.selectionSet.selections.reduce(this.reduceSelection, acc);
|
147 | }
|
148 | return acc;
|
149 | };
|
150 | this.isGlobalType = (type) => {
|
151 | return (type instanceof graphql_1.GraphQLEnumType || type instanceof graphql_1.GraphQLInputObjectType);
|
152 | };
|
153 | this.getUnderlyingType = (type) => {
|
154 | if (type instanceof graphql_2.GraphQLNonNull) {
|
155 | return this.getUnderlyingType(graphql_2.getNullableType(type));
|
156 | }
|
157 | if (type instanceof graphql_2.GraphQLList) {
|
158 | return this.getUnderlyingType(type.ofType);
|
159 | }
|
160 | return type;
|
161 | };
|
162 | this.reduceTypesUsed = (acc, type) => {
|
163 | if (type instanceof graphql_2.GraphQLNonNull) {
|
164 | type = graphql_2.getNullableType(type);
|
165 | }
|
166 | if (type instanceof graphql_2.GraphQLList) {
|
167 | type = type.ofType;
|
168 | }
|
169 | if (type instanceof graphql_1.GraphQLInputObjectType ||
|
170 | type instanceof graphql_2.GraphQLObjectType) {
|
171 | acc = array_1.maybePush(acc, type);
|
172 | const fields = type.getFields();
|
173 | acc = Object.keys(fields)
|
174 | .map(key => fields[key] && fields[key].type)
|
175 | .reduce(this.reduceTypesUsed, acc);
|
176 | }
|
177 | else {
|
178 | acc = array_1.maybePush(acc, type);
|
179 | }
|
180 | return acc;
|
181 | };
|
182 | this.context = context;
|
183 | this.printer = new printer_1.default();
|
184 | this.scopeStack = [];
|
185 | }
|
186 | fileHeader() {
|
187 | this.printer.enqueue(common_tags_1.stripIndent `
|
188 | /* tslint:disable */
|
189 | // This file was automatically generated and should not be edited.
|
190 | `);
|
191 | }
|
192 | typeAliasForEnumType(enumType) {
|
193 | this.printer.enqueue(this.enumerationDeclaration(enumType));
|
194 | }
|
195 | typeAliasForInputObjectType(inputObjectType) {
|
196 | this.printer.enqueue(this.inputObjectDeclaration(inputObjectType));
|
197 | }
|
198 | interfacesForOperation(operation) {
|
199 | const { operationType, operationName, variables, selectionSet } = operation;
|
200 | this.scopeStackPush(operationName);
|
201 | this.printer.enqueue(common_tags_1.stripIndent `
|
202 | // ====================================================
|
203 | // GraphQL ${operationType} operation: ${operationName}
|
204 | // ====================================================
|
205 | `);
|
206 | const variants = this.getVariantsForSelectionSet(selectionSet);
|
207 | const variant = variants[0];
|
208 | const properties = this.getPropertiesForVariant(variant);
|
209 | const exportedTypeAlias = this.exportDeclaration(this.interface(operationName, properties));
|
210 | this.printer.enqueue(exportedTypeAlias);
|
211 | this.scopeStackPop();
|
212 | if (variables.length > 0) {
|
213 | const interfaceName = operationName + "Variables";
|
214 | this.scopeStackPush(interfaceName);
|
215 | this.printer.enqueue(this.exportDeclaration(this.interface(interfaceName, variables.map(variable => ({
|
216 | name: variable.name,
|
217 | type: this.typeFromGraphQLType(variable.type)
|
218 | })), { keyInheritsNullability: true })));
|
219 | this.scopeStackPop();
|
220 | }
|
221 | }
|
222 | interfacesForFragment(fragment) {
|
223 | const { fragmentName, selectionSet } = fragment;
|
224 | this.scopeStackPush(fragmentName);
|
225 | this.printer.enqueue(common_tags_1.stripIndent `
|
226 | // ====================================================
|
227 | // GraphQL fragment: ${fragmentName}
|
228 | // ====================================================
|
229 | `);
|
230 | const variants = this.getVariantsForSelectionSet(selectionSet);
|
231 | if (variants.length === 1) {
|
232 | const properties = this.getPropertiesForVariant(variants[0]);
|
233 | const name = this.nameFromScopeStack(this.scopeStack);
|
234 | const exportedTypeAlias = this.exportDeclaration(this.interface(name, properties));
|
235 | this.printer.enqueue(exportedTypeAlias);
|
236 | }
|
237 | else {
|
238 | const unionMembers = [];
|
239 | variants.forEach(variant => {
|
240 | this.scopeStackPush(variant.possibleTypes[0].toString());
|
241 | const properties = this.getPropertiesForVariant(variant);
|
242 | const name = this.nameFromScopeStack(this.scopeStack);
|
243 | const exportedTypeAlias = this.exportDeclaration(this.interface(name, properties));
|
244 | this.printer.enqueue(exportedTypeAlias);
|
245 | unionMembers.push(t.identifier(this.nameFromScopeStack(this.scopeStack)));
|
246 | this.scopeStackPop();
|
247 | });
|
248 | this.printer.enqueue(this.exportDeclaration(this.typeAliasGenericUnion(this.nameFromScopeStack(this.scopeStack), unionMembers.map(id => t.TSTypeReference(id)))));
|
249 | }
|
250 | this.scopeStackPop();
|
251 | }
|
252 | getTypesUsedForOperation(doc, context) {
|
253 | let docTypesUsed = [];
|
254 | if (doc.hasOwnProperty("operationName")) {
|
255 | const operation = doc;
|
256 | docTypesUsed = operation.variables.map(({ type }) => type);
|
257 | }
|
258 | const reduceTypesForDocument = (nestDoc, acc) => {
|
259 | const { selectionSet: { possibleTypes, selections } } = nestDoc;
|
260 | acc = possibleTypes.reduce(array_1.maybePush, acc);
|
261 | acc = selections.reduce((selectionAcc, selection) => {
|
262 | switch (selection.kind) {
|
263 | case "Field":
|
264 | case "TypeCondition":
|
265 | selectionAcc = array_1.maybePush(selectionAcc, selection.type);
|
266 | break;
|
267 | case "FragmentSpread":
|
268 | selectionAcc = reduceTypesForDocument(selection, selectionAcc);
|
269 | break;
|
270 | default:
|
271 | break;
|
272 | }
|
273 | return selectionAcc;
|
274 | }, acc);
|
275 | return acc;
|
276 | };
|
277 | docTypesUsed = reduceTypesForDocument(doc, docTypesUsed).reduce(this.reduceTypesUsed, []);
|
278 | return context.typesUsed.filter(type => {
|
279 | return docTypesUsed.find(typeUsed => type === typeUsed);
|
280 | });
|
281 | }
|
282 | getVariantsForSelectionSet(selectionSet) {
|
283 | return this.getTypeCasesForSelectionSet(selectionSet).exhaustiveVariants;
|
284 | }
|
285 | getTypeCasesForSelectionSet(selectionSet) {
|
286 | return typeCase_1.typeCaseForSelectionSet(selectionSet, this.context.options.mergeInFieldsFromFragmentSpreads);
|
287 | }
|
288 | getPropertiesForVariant(variant) {
|
289 | const fields = collectAndMergeFields_1.collectAndMergeFields(variant, this.context.options.mergeInFieldsFromFragmentSpreads);
|
290 | return fields.map(field => {
|
291 | const fieldName = field.alias !== undefined ? field.alias : field.name;
|
292 | this.scopeStackPush(fieldName);
|
293 | let res;
|
294 | if (field.selectionSet) {
|
295 | res = this.handleFieldSelectionSetValue(t.identifier(this.nameFromScopeStack(this.scopeStack)), field);
|
296 | }
|
297 | else {
|
298 | res = this.handleFieldValue(field, variant);
|
299 | }
|
300 | this.scopeStackPop();
|
301 | return res;
|
302 | });
|
303 | }
|
304 | handleFieldSelectionSetValue(generatedIdentifier, field) {
|
305 | const { selectionSet } = field;
|
306 | const type = this.typeFromGraphQLType(field.type, generatedIdentifier.name);
|
307 | const typeCase = this.getTypeCasesForSelectionSet(selectionSet);
|
308 | const variants = typeCase.exhaustiveVariants;
|
309 | let exportedTypeAlias;
|
310 | if (variants.length === 1) {
|
311 | const variant = variants[0];
|
312 | const properties = this.getPropertiesForVariant(variant);
|
313 | exportedTypeAlias = this.exportDeclaration(this.interface(this.nameFromScopeStack(this.scopeStack), properties));
|
314 | }
|
315 | else {
|
316 | const identifiers = variants.map(variant => {
|
317 | this.scopeStackPush(variant.possibleTypes[0].toString());
|
318 | const properties = this.getPropertiesForVariant(variant);
|
319 | const identifierName = this.nameFromScopeStack(this.scopeStack);
|
320 | this.printer.enqueue(this.exportDeclaration(this.interface(identifierName, properties)));
|
321 | this.scopeStackPop();
|
322 | return t.identifier(identifierName);
|
323 | });
|
324 | exportedTypeAlias = this.exportDeclaration(this.typeAliasGenericUnion(generatedIdentifier.name, identifiers.map(i => t.TSTypeReference(i))));
|
325 | }
|
326 | this.printer.enqueue(exportedTypeAlias);
|
327 | return {
|
328 | name: field.alias ? field.alias : field.name,
|
329 | description: field.description,
|
330 | type
|
331 | };
|
332 | }
|
333 | handleFieldValue(field, variant) {
|
334 | let res;
|
335 | if (field.name === "__typename") {
|
336 | const types = variant.possibleTypes.map(type => {
|
337 | return t.TSLiteralType(t.stringLiteral(type.toString()));
|
338 | });
|
339 | res = {
|
340 | name: field.alias ? field.alias : field.name,
|
341 | description: field.description,
|
342 | type: t.TSUnionType(types)
|
343 | };
|
344 | }
|
345 | else {
|
346 | res = {
|
347 | name: field.alias ? field.alias : field.name,
|
348 | description: field.description,
|
349 | type: this.typeFromGraphQLType(field.type)
|
350 | };
|
351 | }
|
352 | return res;
|
353 | }
|
354 | get output() {
|
355 | return this.printer.print();
|
356 | }
|
357 | scopeStackPush(name) {
|
358 | this.scopeStack.push(name);
|
359 | }
|
360 | scopeStackPop() {
|
361 | const popped = this.scopeStack.pop();
|
362 | return popped;
|
363 | }
|
364 | }
|
365 | exports.TypescriptAPIGenerator = TypescriptAPIGenerator;
|
366 |
|
\ | No newline at end of file |