apollo-codegen-typescript
Version:
TypeScript generator module for Apollo Codegen
396 lines • 18 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypescriptAPIGenerator = exports.generateGlobalSource = exports.generateLocalSource = exports.generateSource = void 0;
const path_1 = __importDefault(require("path"));
const t = __importStar(require("@babel/types"));
const common_tags_1 = require("common-tags");
const typeCase_1 = require("apollo-codegen-core/lib/compiler/visitors/typeCase");
const collectAndMergeFields_1 = require("apollo-codegen-core/lib/compiler/visitors/collectAndMergeFields");
const language_1 = __importDefault(require("./language"));
const printer_1 = __importDefault(require("./printer"));
const helpers_1 = require("./helpers");
const definition_1 = require("graphql/type/definition");
const graphql_1 = require("graphql");
const array_1 = require("apollo-codegen-core/lib/utilities/array");
const printing_1 = require("apollo-codegen-core/lib/utilities/printing");
class TypescriptGeneratedFile {
constructor(fileContents) {
this.fileContents = fileContents;
}
get output() {
return this.fileContents;
}
}
function printEnumsAndInputObjects(generator, typesUsed) {
generator.printer.enqueue((0, common_tags_1.stripIndent) `
//==============================================================
// START Enums and Input Objects
//==============================================================
`);
typesUsed
.filter(definition_1.isEnumType)
.sort()
.forEach((enumType) => {
generator.typeAliasForEnumType(enumType);
});
typesUsed
.filter(definition_1.isInputObjectType)
.sort()
.forEach((inputObjectType) => {
generator.typeAliasForInputObjectType(inputObjectType);
});
generator.printer.enqueue((0, common_tags_1.stripIndent) `
//==============================================================
// END Enums and Input Objects
//==============================================================
`);
}
function printGlobalImport(generator, typesUsed, outputPath, tsFileExtension, globalSourcePath) {
if (typesUsed.length > 0) {
const relative = path_1.default.relative(path_1.default.dirname(outputPath), path_1.default.join(path_1.default.dirname(globalSourcePath), path_1.default.basename(globalSourcePath, `.${tsFileExtension}`)));
generator.printer.enqueue(generator.import(typesUsed, "./" + (0, printing_1.unifyPaths)(relative)));
}
}
function generateSource(context) {
const generator = new TypescriptAPIGenerator(context);
const generatedFiles = [];
Object.values(context.operations).forEach((operation) => {
generator.fileHeader();
generator.interfacesForOperation(operation);
const output = generator.printer.printAndClear();
generatedFiles.push({
sourcePath: operation.filePath,
fileName: `${operation.operationName}.${context.options.tsFileExtension || helpers_1.DEFAULT_FILE_EXTENSION}`,
content: new TypescriptGeneratedFile(output),
});
});
Object.values(context.fragments).forEach((fragment) => {
generator.fileHeader();
generator.interfacesForFragment(fragment);
const output = generator.printer.printAndClear();
generatedFiles.push({
sourcePath: fragment.filePath,
fileName: `${fragment.fragmentName}.ts`,
content: new TypescriptGeneratedFile(output),
});
});
generator.fileHeader();
printEnumsAndInputObjects(generator, context.typesUsed);
const common = generator.printer.printAndClear();
return {
generatedFiles,
common,
};
}
exports.generateSource = generateSource;
function generateLocalSource(context) {
const generator = new TypescriptAPIGenerator(context);
const operations = Object.values(context.operations).map((operation) => ({
sourcePath: operation.filePath,
fileName: `${operation.operationName}.${context.options.tsFileExtension || helpers_1.DEFAULT_FILE_EXTENSION}`,
content: (options) => {
generator.fileHeader();
if (options && options.outputPath && options.globalSourcePath) {
printGlobalImport(generator, generator.getGlobalTypesUsedForOperation(operation), options.outputPath, context.options.tsFileExtension || helpers_1.DEFAULT_FILE_EXTENSION, options.globalSourcePath);
}
generator.interfacesForOperation(operation);
const output = generator.printer.printAndClear();
return new TypescriptGeneratedFile(output);
},
}));
const fragments = Object.values(context.fragments).map((fragment) => ({
sourcePath: fragment.filePath,
fileName: `${fragment.fragmentName}.${context.options.tsFileExtension || helpers_1.DEFAULT_FILE_EXTENSION}`,
content: (options) => {
generator.fileHeader();
if (options && options.outputPath && options.globalSourcePath) {
printGlobalImport(generator, generator.getGlobalTypesUsedForFragment(fragment), options.outputPath, context.options.tsFileExtension || helpers_1.DEFAULT_FILE_EXTENSION, options.globalSourcePath);
}
generator.interfacesForFragment(fragment);
const output = generator.printer.printAndClear();
return new TypescriptGeneratedFile(output);
},
}));
return operations.concat(fragments);
}
exports.generateLocalSource = generateLocalSource;
function generateGlobalSource(context) {
const generator = new TypescriptAPIGenerator(context);
generator.fileHeader();
printEnumsAndInputObjects(generator, context.typesUsed);
const output = generator.printer.printAndClear();
return new TypescriptGeneratedFile(output);
}
exports.generateGlobalSource = generateGlobalSource;
class TypescriptAPIGenerator extends language_1.default {
constructor(context) {
super(context.options);
this.getGlobalTypesUsedForOperation = (doc) => {
const typesUsed = doc.variables.reduce((acc, { type }) => {
const t = this.getUnderlyingType(type);
if (this.isGlobalType(t)) {
return (0, array_1.maybePush)(acc, t);
}
return acc;
}, []);
return doc.selectionSet.selections.reduce(this.reduceSelection, typesUsed);
};
this.getGlobalTypesUsedForFragment = (doc) => {
return doc.selectionSet.selections.reduce(this.reduceSelection, []);
};
this.reduceSelection = (acc, selection) => {
if (selection.kind === "Field" || selection.kind === "TypeCondition") {
const type = this.getUnderlyingType(selection.type);
if (this.isGlobalType(type)) {
acc = (0, array_1.maybePush)(acc, type);
}
}
if (selection.selectionSet) {
return selection.selectionSet.selections.reduce(this.reduceSelection, acc);
}
return acc;
};
this.isGlobalType = (type) => {
return (0, definition_1.isEnumType)(type) || (0, definition_1.isInputObjectType)(type);
};
this.getUnderlyingType = (type) => {
if ((0, definition_1.isNonNullType)(type)) {
return this.getUnderlyingType((0, graphql_1.getNullableType)(type));
}
if ((0, definition_1.isListType)(type)) {
return this.getUnderlyingType(type.ofType);
}
return type;
};
this.reduceTypesUsed = (acc, type) => {
if ((0, definition_1.isNonNullType)(type)) {
type = (0, graphql_1.getNullableType)(type);
}
if ((0, definition_1.isListType)(type)) {
type = type.ofType;
}
if ((0, definition_1.isInputObjectType)(type) || (0, definition_1.isObjectType)(type)) {
acc = (0, array_1.maybePush)(acc, type);
const fields = type.getFields();
acc = Object.keys(fields)
.map((key) => fields[key] && fields[key].type)
.reduce(this.reduceTypesUsed, acc);
}
else {
acc = (0, array_1.maybePush)(acc, type);
}
return acc;
};
this.context = context;
this.printer = new printer_1.default();
this.scopeStack = [];
}
fileHeader() {
this.printer.enqueue((0, common_tags_1.stripIndent) `
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
`);
}
typeAliasForEnumType(enumType) {
this.printer.enqueue(this.enumerationDeclaration(enumType));
}
typeAliasForInputObjectType(inputObjectType) {
this.printer.enqueue(this.inputObjectDeclaration(inputObjectType));
}
interfacesForOperation(operation) {
const { operationType, operationName, variables, selectionSet } = operation;
this.scopeStackPush(operationName);
this.printer.enqueue((0, common_tags_1.stripIndent) `
// ====================================================
// GraphQL ${operationType} operation: ${operationName}
// ====================================================
`);
const variants = this.getVariantsForSelectionSet(selectionSet);
const variant = variants[0];
const properties = this.getPropertiesForVariant(variant);
const exportedTypeAlias = this.exportDeclaration(this.interface(operationName, properties));
this.printer.enqueue(exportedTypeAlias);
this.scopeStackPop();
if (variables.length > 0) {
const interfaceName = operationName + "Variables";
this.scopeStackPush(interfaceName);
this.printer.enqueue(this.exportDeclaration(this.interface(interfaceName, variables.map((variable) => ({
name: variable.name,
type: this.typeFromGraphQLType(variable.type),
})), { keyInheritsNullability: true })));
this.scopeStackPop();
}
}
interfacesForFragment(fragment) {
const { fragmentName, selectionSet } = fragment;
this.scopeStackPush(fragmentName);
this.printer.enqueue((0, common_tags_1.stripIndent) `
// ====================================================
// GraphQL fragment: ${fragmentName}
// ====================================================
`);
const variants = this.getVariantsForSelectionSet(selectionSet);
if (variants.length === 1) {
const properties = this.getPropertiesForVariant(variants[0]);
const name = this.nameFromScopeStack(this.scopeStack);
const exportedTypeAlias = this.exportDeclaration(this.interface(name, properties));
this.printer.enqueue(exportedTypeAlias);
}
else {
const unionMembers = [];
variants.forEach((variant) => {
this.scopeStackPush(variant.possibleTypes[0].toString());
const properties = this.getPropertiesForVariant(variant);
const name = this.nameFromScopeStack(this.scopeStack);
const exportedTypeAlias = this.exportDeclaration(this.interface(name, properties));
this.printer.enqueue(exportedTypeAlias);
unionMembers.push(t.identifier(this.nameFromScopeStack(this.scopeStack)));
this.scopeStackPop();
});
this.printer.enqueue(this.exportDeclaration(this.typeAliasGenericUnion(this.nameFromScopeStack(this.scopeStack), unionMembers.map((id) => t.TSTypeReference(id)))));
}
this.scopeStackPop();
}
getTypesUsedForOperation(doc, context) {
let docTypesUsed = [];
if (doc.hasOwnProperty("operationName")) {
const operation = doc;
docTypesUsed = operation.variables.map(({ type }) => type);
}
const reduceTypesForDocument = (nestDoc, acc) => {
const { selectionSet: { possibleTypes, selections }, } = nestDoc;
acc = possibleTypes.reduce(array_1.maybePush, acc);
acc = selections.reduce((selectionAcc, selection) => {
switch (selection.kind) {
case "Field":
case "TypeCondition":
selectionAcc = (0, array_1.maybePush)(selectionAcc, selection.type);
break;
case "FragmentSpread":
selectionAcc = reduceTypesForDocument(selection, selectionAcc);
break;
default:
break;
}
return selectionAcc;
}, acc);
return acc;
};
docTypesUsed = reduceTypesForDocument(doc, docTypesUsed).reduce(this.reduceTypesUsed, []);
return context.typesUsed.filter((type) => {
return docTypesUsed.find((typeUsed) => type === typeUsed);
});
}
getVariantsForSelectionSet(selectionSet) {
return this.getTypeCasesForSelectionSet(selectionSet).exhaustiveVariants;
}
getTypeCasesForSelectionSet(selectionSet) {
return (0, typeCase_1.typeCaseForSelectionSet)(selectionSet, this.context.options.mergeInFieldsFromFragmentSpreads);
}
getPropertiesForVariant(variant) {
const fields = (0, collectAndMergeFields_1.collectAndMergeFields)(variant, this.context.options.mergeInFieldsFromFragmentSpreads);
return fields.map((field) => {
const fieldName = field.alias !== undefined ? field.alias : field.name;
this.scopeStackPush(fieldName);
let res;
if (field.selectionSet) {
res = this.handleFieldSelectionSetValue(t.identifier(this.nameFromScopeStack(this.scopeStack)), field);
}
else {
res = this.handleFieldValue(field, variant);
}
this.scopeStackPop();
return res;
});
}
handleFieldSelectionSetValue(generatedIdentifier, field) {
const { selectionSet } = field;
const type = this.typeFromGraphQLType(field.type, generatedIdentifier.name);
const typeCase = this.getTypeCasesForSelectionSet(selectionSet);
const variants = typeCase.exhaustiveVariants;
let exportedTypeAlias;
if (variants.length === 1) {
const variant = variants[0];
const properties = this.getPropertiesForVariant(variant);
exportedTypeAlias = this.exportDeclaration(this.interface(this.nameFromScopeStack(this.scopeStack), properties));
}
else {
const identifiers = variants.map((variant) => {
this.scopeStackPush(variant.possibleTypes[0].toString());
const properties = this.getPropertiesForVariant(variant);
const identifierName = this.nameFromScopeStack(this.scopeStack);
this.printer.enqueue(this.exportDeclaration(this.interface(identifierName, properties)));
this.scopeStackPop();
return t.identifier(identifierName);
});
exportedTypeAlias = this.exportDeclaration(this.typeAliasGenericUnion(generatedIdentifier.name, identifiers.map((i) => t.TSTypeReference(i))));
}
this.printer.enqueue(exportedTypeAlias);
return {
name: field.alias ? field.alias : field.name,
description: field.description,
type,
};
}
handleFieldValue(field, variant) {
let res;
if (field.name === "__typename") {
const types = variant.possibleTypes.map((type) => {
return t.TSLiteralType(t.stringLiteral(type.toString()));
});
res = {
name: field.alias ? field.alias : field.name,
description: field.description,
type: t.TSUnionType(types),
};
}
else {
res = {
name: field.alias ? field.alias : field.name,
description: field.description,
type: this.typeFromGraphQLType(field.type),
};
}
return res;
}
get output() {
return this.printer.print();
}
scopeStackPush(name) {
this.scopeStack.push(name);
}
scopeStackPop() {
const popped = this.scopeStack.pop();
return popped;
}
}
exports.TypescriptAPIGenerator = TypescriptAPIGenerator;
//# sourceMappingURL=codeGeneration.js.map