UNPKG

15.2 kBJavaScriptView Raw
1import { getCachedDocumentNodeFromSchema } from '@graphql-codegen/plugin-helpers';
2import { GraphQLEnumType, visit } from 'graphql';
3import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
4import autoBind from 'auto-bind';
5import { TsVisitor, TypeScriptOperationVariablesToObject, includeIntrospectionDefinitions } from '@graphql-codegen/typescript';
6export { TsIntrospectionVisitor } from '@graphql-codegen/typescript';
7
8const MAYBE_REGEX = /^Maybe<(.*?)>$/;
9const ARRAY_REGEX = /^Array<(.*?)>$/;
10const SCALAR_REGEX = /^Scalars\['(.*?)'\]$/;
11const GRAPHQL_TYPES = ['Query', 'Mutation', 'Subscription'];
12const SCALARS = ['ID', 'String', 'Boolean', 'Int', 'Float'];
13const TYPE_GRAPHQL_SCALARS = ['ID', 'Int', 'Float'];
14function escapeString(str) {
15 return ("'" +
16 String(str || '')
17 .replace(/\\/g, '\\\\')
18 .replace(/\n/g, '\\n')
19 .replace(/'/g, "\\'") +
20 "'");
21}
22function formatDecoratorOptions(options, isFirstArgument = true) {
23 if (!Object.keys(options).length) {
24 return '';
25 }
26 else {
27 return ((isFirstArgument ? '' : ', ') +
28 ('{ ' +
29 Object.entries(options)
30 .map(([key, value]) => `${key}: ${value}`)
31 .join(', ') +
32 ' }'));
33 }
34}
35const FIX_DECORATOR_SIGNATURE = `type FixDecorator<T> = T;`;
36function getTypeGraphQLNullableValue(type) {
37 if (type.isNullable) {
38 if (type.isItemsNullable) {
39 return "'itemsAndList'";
40 }
41 else {
42 return 'true';
43 }
44 }
45 else if (type.isItemsNullable) {
46 return "'items'";
47 }
48 return undefined;
49}
50class TypeGraphQLVisitor extends TsVisitor {
51 constructor(schema, pluginConfig, additionalConfig = {}) {
52 super(schema, pluginConfig, {
53 avoidOptionals: pluginConfig.avoidOptionals || false,
54 maybeValue: pluginConfig.maybeValue || 'T | null',
55 constEnums: pluginConfig.constEnums || false,
56 enumsAsTypes: pluginConfig.enumsAsTypes || false,
57 immutableTypes: pluginConfig.immutableTypes || false,
58 declarationKind: {
59 type: 'class',
60 interface: 'abstract class',
61 arguments: 'class',
62 input: 'class',
63 scalar: 'type',
64 },
65 decoratorName: {
66 type: 'ObjectType',
67 interface: 'InterfaceType',
68 arguments: 'ArgsType',
69 field: 'Field',
70 input: 'InputType',
71 ...(pluginConfig.decoratorName || {}),
72 },
73 decorateTypes: pluginConfig.decorateTypes || undefined,
74 ...(additionalConfig || {}),
75 });
76 autoBind(this);
77 this.typescriptVisitor = new TsVisitor(schema, pluginConfig, additionalConfig);
78 const enumNames = Object.values(schema.getTypeMap())
79 .map(type => (type instanceof GraphQLEnumType ? type.name : undefined))
80 .filter(t => t);
81 this.setArgumentsTransformer(new TypeScriptOperationVariablesToObject(this.scalars, this.convertName, this.config.avoidOptionals, this.config.immutableTypes, null, enumNames, this.config.enumPrefix, this.config.enumValues));
82 this.setDeclarationBlockConfig({
83 enumNameValueSeparator: ' =',
84 });
85 }
86 getDecoratorOptions(node) {
87 const decoratorOptions = {};
88 if (node.description) {
89 // Add description as TypeGraphQL description instead of comment
90 decoratorOptions.description = escapeString(node.description);
91 node.description = undefined;
92 }
93 return decoratorOptions;
94 }
95 getWrapperDefinitions() {
96 return [...super.getWrapperDefinitions(), this.getFixDecoratorDefinition()];
97 }
98 getFixDecoratorDefinition() {
99 return `${this.getExportPrefix()}${FIX_DECORATOR_SIGNATURE}`;
100 }
101 buildArgumentsBlock(node) {
102 const fieldsWithArguments = node.fields.filter(field => field.arguments && field.arguments.length > 0) || [];
103 return fieldsWithArguments
104 .map(field => {
105 const name = node.name.value +
106 (this.config.addUnderscoreToArgsType ? '_' : '') +
107 this.convertName(field, {
108 useTypesPrefix: false,
109 useTypesSuffix: false,
110 }) +
111 'Args';
112 if (this.hasTypeDecorators(name)) {
113 return this.getArgumentsObjectTypeDefinition(node, name, field);
114 }
115 else {
116 return this.typescriptVisitor.getArgumentsObjectTypeDefinition(node, name, field);
117 }
118 })
119 .join('\n\n');
120 }
121 ObjectTypeDefinition(node, key, parent) {
122 const isGraphQLType = GRAPHQL_TYPES.includes(node.name);
123 if (!isGraphQLType && !this.hasTypeDecorators(node.name)) {
124 return this.typescriptVisitor.ObjectTypeDefinition(node, key, parent);
125 }
126 const typeDecorator = this.config.decoratorName.type;
127 const originalNode = parent[key];
128 const decoratorOptions = this.getDecoratorOptions(node);
129 let declarationBlock;
130 if (isGraphQLType) {
131 declarationBlock = this.typescriptVisitor.getObjectTypeDeclarationBlock(node, originalNode);
132 }
133 else {
134 declarationBlock = this.getObjectTypeDeclarationBlock(node, originalNode);
135 // Add type-graphql ObjectType decorator
136 const interfaces = originalNode.interfaces.map(i => this.convertName(i));
137 if (interfaces.length > 1) {
138 decoratorOptions.implements = `[${interfaces.join(', ')}]`;
139 }
140 else if (interfaces.length === 1) {
141 decoratorOptions.implements = interfaces[0];
142 }
143 declarationBlock = declarationBlock.withDecorator(`@TypeGraphQL.${typeDecorator}(${formatDecoratorOptions(decoratorOptions)})`);
144 }
145 return [declarationBlock.string, this.buildArgumentsBlock(originalNode)].filter(f => f).join('\n\n');
146 }
147 InputObjectTypeDefinition(node) {
148 if (!this.hasTypeDecorators(node.name)) {
149 return this.typescriptVisitor.InputObjectTypeDefinition(node);
150 }
151 const typeDecorator = this.config.decoratorName.input;
152 const decoratorOptions = this.getDecoratorOptions(node);
153 let declarationBlock = this.getInputObjectDeclarationBlock(node);
154 // Add type-graphql InputType decorator
155 declarationBlock = declarationBlock.withDecorator(`@TypeGraphQL.${typeDecorator}(${formatDecoratorOptions(decoratorOptions)})`);
156 return declarationBlock.string;
157 }
158 getArgumentsObjectDeclarationBlock(node, name, field) {
159 return new DeclarationBlock(this._declarationBlockConfig)
160 .export()
161 .asKind(this._parsedConfig.declarationKind.arguments)
162 .withName(this.convertName(name))
163 .withComment(node.description)
164 .withBlock(field.arguments.map(argument => this.InputValueDefinition(argument)).join('\n'));
165 }
166 getArgumentsObjectTypeDefinition(node, name, field) {
167 const typeDecorator = this.config.decoratorName.arguments;
168 let declarationBlock = this.getArgumentsObjectDeclarationBlock(node, name, field);
169 // Add type-graphql Args decorator
170 declarationBlock = declarationBlock.withDecorator(`@TypeGraphQL.${typeDecorator}()`);
171 return declarationBlock.string;
172 }
173 InterfaceTypeDefinition(node, key, parent) {
174 if (!this.hasTypeDecorators(node.name)) {
175 return this.typescriptVisitor.InterfaceTypeDefinition(node, key, parent);
176 }
177 const interfaceDecorator = this.config.decoratorName.interface;
178 const originalNode = parent[key];
179 const decoratorOptions = this.getDecoratorOptions(node);
180 const declarationBlock = this.getInterfaceTypeDeclarationBlock(node, originalNode).withDecorator(`@TypeGraphQL.${interfaceDecorator}(${formatDecoratorOptions(decoratorOptions)})`);
181 return [declarationBlock.string, this.buildArgumentsBlock(originalNode)].filter(f => f).join('\n\n');
182 }
183 buildTypeString(type) {
184 if (!type.isArray && !type.isScalar && !type.isNullable) {
185 type.type = `FixDecorator<${type.type}>`;
186 }
187 if (type.isScalar) {
188 type.type = `Scalars['${type.type}']`;
189 }
190 if (type.isArray) {
191 type.type = `Array<${type.type}>`;
192 }
193 if (type.isNullable) {
194 type.type = `Maybe<${type.type}>`;
195 }
196 return type.type;
197 }
198 parseType(rawType) {
199 const typeNode = rawType;
200 if (typeNode.kind === 'NamedType') {
201 return {
202 type: typeNode.name.value,
203 isNullable: true,
204 isArray: false,
205 isItemsNullable: false,
206 isScalar: SCALARS.includes(typeNode.name.value),
207 };
208 }
209 else if (typeNode.kind === 'NonNullType') {
210 return {
211 ...this.parseType(typeNode.type),
212 isNullable: false,
213 };
214 }
215 else if (typeNode.kind === 'ListType') {
216 return {
217 ...this.parseType(typeNode.type),
218 isArray: true,
219 isNullable: true,
220 };
221 }
222 const isNullable = !!rawType.match(MAYBE_REGEX);
223 const nonNullableType = rawType.replace(MAYBE_REGEX, '$1');
224 const isArray = !!nonNullableType.match(ARRAY_REGEX);
225 const singularType = nonNullableType.replace(ARRAY_REGEX, '$1');
226 const isSingularTypeNullable = !!singularType.match(MAYBE_REGEX);
227 const singularNonNullableType = singularType.replace(MAYBE_REGEX, '$1');
228 const isScalar = !!singularNonNullableType.match(SCALAR_REGEX);
229 const type = singularNonNullableType.replace(SCALAR_REGEX, (match, type) => {
230 if (TYPE_GRAPHQL_SCALARS.includes(type)) {
231 // This is a TypeGraphQL type
232 return `TypeGraphQL.${type}`;
233 }
234 else if (global[type]) {
235 // This is a JS native type
236 return type;
237 }
238 else if (this.scalars[type]) {
239 // This is a type specified in the configuration
240 return this.scalars[type];
241 }
242 else {
243 throw new Error(`Unknown scalar type ${type}`);
244 }
245 });
246 return { type, isNullable, isArray, isScalar, isItemsNullable: isArray && isSingularTypeNullable };
247 }
248 fixDecorator(type, typeString) {
249 return type.isArray || type.isNullable || type.isScalar ? typeString : `FixDecorator<${typeString}>`;
250 }
251 FieldDefinition(node, key, parent, path, ancestors) {
252 const parentName = ancestors === null || ancestors === void 0 ? void 0 : ancestors[ancestors.length - 1].name.value;
253 if (!this.hasTypeDecorators(parentName)) {
254 return this.typescriptVisitor.FieldDefinition(node, key, parent);
255 }
256 const fieldDecorator = this.config.decoratorName.field;
257 let typeString = node.type;
258 const type = this.parseType(typeString);
259 const decoratorOptions = this.getDecoratorOptions(node);
260 const nullableValue = getTypeGraphQLNullableValue(type);
261 if (nullableValue) {
262 decoratorOptions.nullable = nullableValue;
263 }
264 const decorator = '\n' +
265 indent(`@TypeGraphQL.${fieldDecorator}(type => ${type.isArray ? `[${type.type}]` : type.type}${formatDecoratorOptions(decoratorOptions, false)})`) +
266 '\n';
267 typeString = this.fixDecorator(type, typeString);
268 return decorator + indent(`${this.config.immutableTypes ? 'readonly ' : ''}${node.name}!: ${typeString};`);
269 }
270 InputValueDefinition(node, key, parent, path, ancestors) {
271 const parentName = ancestors === null || ancestors === void 0 ? void 0 : ancestors[ancestors.length - 1].name.value;
272 if (parent && !this.hasTypeDecorators(parentName)) {
273 return this.typescriptVisitor.InputValueDefinition(node, key, parent);
274 }
275 const fieldDecorator = this.config.decoratorName.field;
276 const rawType = node.type;
277 const type = this.parseType(rawType);
278 const typeGraphQLType = type.isScalar && TYPE_GRAPHQL_SCALARS.includes(type.type) ? `TypeGraphQL.${type.type}` : type.type;
279 const decoratorOptions = this.getDecoratorOptions(node);
280 const nullableValue = getTypeGraphQLNullableValue(type);
281 if (nullableValue) {
282 decoratorOptions.nullable = nullableValue;
283 }
284 const decorator = '\n' +
285 indent(`@TypeGraphQL.${fieldDecorator}(type => ${type.isArray ? `[${typeGraphQLType}]` : typeGraphQLType}${formatDecoratorOptions(decoratorOptions, false)})`) +
286 '\n';
287 const nameString = node.name.kind ? node.name.value : node.name;
288 const typeString = rawType.kind
289 ? this.buildTypeString(type)
290 : this.fixDecorator(type, rawType);
291 return decorator + indent(`${this.config.immutableTypes ? 'readonly ' : ''}${nameString}!: ${typeString};`);
292 }
293 EnumTypeDefinition(node) {
294 if (!this.hasTypeDecorators(node.name)) {
295 return this.typescriptVisitor.EnumTypeDefinition(node);
296 }
297 return (super.EnumTypeDefinition(node) +
298 `TypeGraphQL.registerEnumType(${this.convertName(node)}, { name: '${this.convertName(node)}' });\n`);
299 }
300 clearOptional(str) {
301 if (str.startsWith('Maybe')) {
302 return str.replace(/Maybe<(.*?)>$/, '$1');
303 }
304 return str;
305 }
306 hasTypeDecorators(typeName) {
307 if (GRAPHQL_TYPES.includes(typeName)) {
308 return false;
309 }
310 if (!this.config.decorateTypes) {
311 return true;
312 }
313 return this.config.decorateTypes.some(filter => filter === typeName);
314 }
315}
316
317const TYPE_GRAPHQL_IMPORT = `import * as TypeGraphQL from 'type-graphql';\nexport { TypeGraphQL };`;
318const isDefinitionInterface = (definition) => definition.includes('@TypeGraphQL.InterfaceType()');
319const plugin = (schema, documents, config) => {
320 const visitor = new TypeGraphQLVisitor(schema, config);
321 const astNode = getCachedDocumentNodeFromSchema(schema);
322 const visitorResult = visit(astNode, { leave: visitor });
323 const introspectionDefinitions = includeIntrospectionDefinitions(schema, documents, config);
324 const scalars = visitor.scalarsDefinition;
325 const definitions = visitorResult.definitions;
326 // Sort output by interfaces first, classes last to prevent TypeScript errors
327 definitions.sort((definition1, definition2) => +isDefinitionInterface(definition2) - +isDefinitionInterface(definition1));
328 return {
329 prepend: [...visitor.getEnumsImports(), ...visitor.getWrapperDefinitions(), TYPE_GRAPHQL_IMPORT],
330 content: [scalars, ...definitions, ...introspectionDefinitions].join('\n'),
331 };
332};
333
334export { TypeGraphQLVisitor, plugin };