UNPKG

12.8 kBJavaScriptView Raw
1import { printSchemaWithDirectives } from '@graphql-tools/utils';
2import { BaseResolversVisitor, getConfigValue, parseMapper } from '@graphql-codegen/visitor-plugin-common';
3import { addFederationReferencesToSchema } from '@graphql-codegen/plugin-helpers';
4import { printSchema, parse, visit } from 'graphql';
5import autoBind from 'auto-bind';
6import { TypeScriptOperationVariablesToObject } from '@graphql-codegen/typescript';
7
8const ENUM_RESOLVERS_SIGNATURE = 'export type EnumResolverSignature<T, AllowedValues = any> = { [key in keyof T]?: AllowedValues };';
9class TypeScriptResolversVisitor extends BaseResolversVisitor {
10 constructor(pluginConfig, schema) {
11 super(pluginConfig, {
12 avoidOptionals: getConfigValue(pluginConfig.avoidOptionals, false),
13 useIndexSignature: getConfigValue(pluginConfig.useIndexSignature, false),
14 wrapFieldDefinitions: getConfigValue(pluginConfig.wrapFieldDefinitions, false),
15 allowParentTypeOverride: getConfigValue(pluginConfig.allowParentTypeOverride, false),
16 optionalInfoArgument: getConfigValue(pluginConfig.optionalInfoArgument, false),
17 }, schema);
18 autoBind(this);
19 this.setVariablesTransformer(new TypeScriptOperationVariablesToObject(this.scalars, this.convertName, this.config.avoidOptionals, this.config.immutableTypes, this.config.namespacedImportName, [], this.config.enumPrefix, this.config.enumValues));
20 if (this.config.useIndexSignature) {
21 this._declarationBlockConfig = {
22 blockTransformer(block) {
23 return `ResolversObject<${block}>`;
24 },
25 };
26 }
27 }
28 transformParentGenericType(parentType) {
29 if (this.config.allowParentTypeOverride) {
30 return `ParentType = ${parentType}`;
31 }
32 return `ParentType extends ${parentType} = ${parentType}`;
33 }
34 formatRootResolver(schemaTypeName, resolverType, declarationKind) {
35 return `${schemaTypeName}${this.config.avoidOptionals ? '' : '?'}: ${resolverType}${this.getPunctuation(declarationKind)}`;
36 }
37 clearOptional(str) {
38 if (str.startsWith('Maybe')) {
39 return str.replace(/Maybe<(.*?)>$/, '$1');
40 }
41 return str;
42 }
43 ListType(node) {
44 return `Maybe<${super.ListType(node)}>`;
45 }
46 wrapWithListType(str) {
47 return `${this.config.immutableTypes ? 'ReadonlyArray' : 'Array'}<${str}>`;
48 }
49 getParentTypeForSignature(node) {
50 if (this._federation.isResolveReferenceField(node) && this.config.wrapFieldDefinitions) {
51 return 'UnwrappedObject<ParentType>';
52 }
53 return 'ParentType';
54 }
55 NamedType(node) {
56 return `Maybe<${super.NamedType(node)}>`;
57 }
58 NonNullType(node) {
59 const baseValue = super.NonNullType(node);
60 return this.clearOptional(baseValue);
61 }
62 getPunctuation(declarationKind) {
63 return ';';
64 }
65 buildEnumResolverContentBlock(node, mappedEnumType) {
66 const valuesMap = `{ ${(node.values || [])
67 .map(v => `${v.name}${this.config.avoidOptionals ? '' : '?'}: any`)
68 .join(', ')} }`;
69 this._globalDeclarations.add(ENUM_RESOLVERS_SIGNATURE);
70 return `EnumResolverSignature<${valuesMap}, ${mappedEnumType}>`;
71 }
72 buildEnumResolversExplicitMappedValues(node, valuesMapping) {
73 return `{ ${(node.values || [])
74 .map(v => {
75 const valueName = v.name;
76 const mappedValue = valuesMapping[valueName];
77 return `${valueName}: ${typeof mappedValue === 'number' ? mappedValue : `'${mappedValue}'`}`;
78 })
79 .join(', ')} }`;
80 }
81}
82
83const plugin = (schema, documents, config) => {
84 const imports = [];
85 if (!config.customResolveInfo) {
86 imports.push('GraphQLResolveInfo');
87 }
88 const showUnusedMappers = typeof config.showUnusedMappers === 'boolean' ? config.showUnusedMappers : true;
89 const noSchemaStitching = typeof config.noSchemaStitching === 'boolean' ? config.noSchemaStitching : false;
90 if (config.noSchemaStitching === false) {
91 // eslint-disable-next-line no-console
92 console.warn(`The default behavior of 'noSchemaStitching' will be reversed in the next major release. Support for Schema Stitching will be disabled by default.`);
93 }
94 const indexSignature = config.useIndexSignature
95 ? [
96 'export type WithIndex<TObject> = TObject & Record<string, any>;',
97 'export type ResolversObject<TObject> = WithIndex<TObject>;',
98 ].join('\n')
99 : '';
100 const transformedSchema = config.federation ? addFederationReferencesToSchema(schema) : schema;
101 const visitor = new TypeScriptResolversVisitor(config, transformedSchema);
102 const namespacedImportPrefix = visitor.config.namespacedImportName ? `${visitor.config.namespacedImportName}.` : '';
103 const printedSchema = config.federation
104 ? printSchemaWithDirectives(transformedSchema)
105 : printSchema(transformedSchema);
106 const astNode = parse(printedSchema);
107 // runs visitor
108 const visitorResult = visit(astNode, { leave: visitor });
109 const optionalSignForInfoArg = visitor.config.optionalInfoArgument ? '?' : '';
110 const prepend = [];
111 const defsToInclude = [];
112 const legacyStitchingResolverType = `
113export type LegacyStitchingResolver<TResult, TParent, TContext, TArgs> = {
114 fragment: string;
115 resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
116};`;
117 const newStitchingResolverType = `
118export type NewStitchingResolver<TResult, TParent, TContext, TArgs> = {
119 selectionSet: string;
120 resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
121};`;
122 const stitchingResolverType = `export type StitchingResolver<TResult, TParent, TContext, TArgs> = LegacyStitchingResolver<TResult, TParent, TContext, TArgs> | NewStitchingResolver<TResult, TParent, TContext, TArgs>;`;
123 const resolverType = `export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> =`;
124 const resolverFnUsage = `ResolverFn<TResult, TParent, TContext, TArgs>`;
125 const stitchingResolverUsage = `StitchingResolver<TResult, TParent, TContext, TArgs>`;
126 if (visitor.hasFederation()) {
127 if (visitor.config.wrapFieldDefinitions) {
128 defsToInclude.push(`export type UnwrappedObject<T> = {
129 [P in keyof T]: T[P] extends infer R | Promise<infer R> | (() => infer R2 | Promise<infer R2>)
130 ? R & R2 : T[P]
131 };`);
132 }
133 defsToInclude.push(`export type ReferenceResolver<TResult, TReference, TContext> = (
134 reference: TReference,
135 context: TContext,
136 info${optionalSignForInfoArg}: GraphQLResolveInfo
137 ) => Promise<TResult> | TResult;`);
138 defsToInclude.push(`
139 type ScalarCheck<T, S> = S extends true ? T : NullableCheck<T, S>;
140 type NullableCheck<T, S> = Maybe<T> extends T ? Maybe<ListCheck<NonNullable<T>, S>> : ListCheck<T, S>;
141 type ListCheck<T, S> = T extends (infer U)[] ? NullableCheck<U, S>[] : GraphQLRecursivePick<T, S>;
142 export type GraphQLRecursivePick<T, S> = { [K in keyof T & keyof S]: ScalarCheck<T[K], S[K]> };
143 `);
144 }
145 if (noSchemaStitching) {
146 // Resolver = ResolverFn;
147 defsToInclude.push(`${resolverType} ${resolverFnUsage};`);
148 }
149 else {
150 // StitchingResolver
151 // Resolver =
152 // | ResolverFn
153 // | StitchingResolver;
154 defsToInclude.push([
155 legacyStitchingResolverType,
156 newStitchingResolverType,
157 stitchingResolverType,
158 resolverType,
159 ` | ${resolverFnUsage}`,
160 ` | ${stitchingResolverUsage};`,
161 ].join('\n'));
162 }
163 if (config.customResolverFn) {
164 const parsedMapper = parseMapper(config.customResolverFn);
165 if (parsedMapper.isExternal) {
166 const importType = config.useTypeImports ? 'import type' : 'import';
167 if (parsedMapper.default) {
168 prepend.push(`${importType} ResolverFn from '${parsedMapper.source}';`);
169 }
170 else {
171 prepend.push(`${importType} { ${parsedMapper.import} ${parsedMapper.import !== 'ResolverFn' ? 'as ResolverFn ' : ''}} from '${parsedMapper.source}';`);
172 }
173 prepend.push(`export${config.useTypeImports ? ' type' : ''} { ResolverFn };`);
174 }
175 else {
176 prepend.push(`export type ResolverFn<TResult, TParent, TContext, TArgs> = ${parsedMapper.type}`);
177 }
178 }
179 else {
180 const defaultResolverFn = `
181export type ResolverFn<TResult, TParent, TContext, TArgs> = (
182 parent: TParent,
183 args: TArgs,
184 context: TContext,
185 info${optionalSignForInfoArg}: GraphQLResolveInfo
186) => Promise<TResult> | TResult;`;
187 defsToInclude.push(defaultResolverFn);
188 }
189 const header = `${indexSignature}
190
191${visitor.getResolverTypeWrapperSignature()}
192
193${defsToInclude.join('\n')}
194
195export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (
196 parent: TParent,
197 args: TArgs,
198 context: TContext,
199 info${optionalSignForInfoArg}: GraphQLResolveInfo
200) => AsyncIterator<TResult> | Promise<AsyncIterator<TResult>>;
201
202export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (
203 parent: TParent,
204 args: TArgs,
205 context: TContext,
206 info${optionalSignForInfoArg}: GraphQLResolveInfo
207) => TResult | Promise<TResult>;
208
209export interface SubscriptionSubscriberObject<TResult, TKey extends string, TParent, TContext, TArgs> {
210 subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>;
211 resolve?: SubscriptionResolveFn<TResult, { [key in TKey]: TResult }, TContext, TArgs>;
212}
213
214export interface SubscriptionResolverObject<TResult, TParent, TContext, TArgs> {
215 subscribe: SubscriptionSubscribeFn<any, TParent, TContext, TArgs>;
216 resolve: SubscriptionResolveFn<TResult, any, TContext, TArgs>;
217}
218
219export type SubscriptionObject<TResult, TKey extends string, TParent, TContext, TArgs> =
220 | SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs>
221 | SubscriptionResolverObject<TResult, TParent, TContext, TArgs>;
222
223export type SubscriptionResolver<TResult, TKey extends string, TParent = {}, TContext = {}, TArgs = {}> =
224 | ((...args: any[]) => SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>)
225 | SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>;
226
227export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = (
228 parent: TParent,
229 context: TContext,
230 info${optionalSignForInfoArg}: GraphQLResolveInfo
231) => ${namespacedImportPrefix}Maybe<TTypes> | Promise<${namespacedImportPrefix}Maybe<TTypes>>;
232
233export type IsTypeOfResolverFn<T = {}> = (obj: T, info${optionalSignForInfoArg}: GraphQLResolveInfo) => boolean | Promise<boolean>;
234
235export type NextResolverFn<T> = () => Promise<T>;
236
237export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs = {}> = (
238 next: NextResolverFn<TResult>,
239 parent: TParent,
240 args: TArgs,
241 context: TContext,
242 info${optionalSignForInfoArg}: GraphQLResolveInfo
243) => TResult | Promise<TResult>;
244`;
245 const resolversTypeMapping = visitor.buildResolversTypes();
246 const resolversParentTypeMapping = visitor.buildResolversParentTypes();
247 const { getRootResolver, getAllDirectiveResolvers, mappersImports, unusedMappers, hasScalars } = visitor;
248 if (hasScalars()) {
249 imports.push('GraphQLScalarType', 'GraphQLScalarTypeConfig');
250 }
251 if (showUnusedMappers && unusedMappers.length) {
252 // eslint-disable-next-line no-console
253 console.warn(`Unused mappers: ${unusedMappers.join(',')}`);
254 }
255 if (imports.length) {
256 prepend.push(`import { ${imports.join(', ')} } from 'graphql';`);
257 }
258 if (config.customResolveInfo) {
259 const parsedMapper = parseMapper(config.customResolveInfo);
260 if (parsedMapper.isExternal) {
261 if (parsedMapper.default) {
262 prepend.push(`import GraphQLResolveInfo from '${parsedMapper.source}'`);
263 }
264 prepend.push(`import { ${parsedMapper.import} ${parsedMapper.import !== 'GraphQLResolveInfo' ? 'as GraphQLResolveInfo' : ''} } from '${parsedMapper.source}';`);
265 }
266 else {
267 prepend.push(`type GraphQLResolveInfo = ${parsedMapper.type}`);
268 }
269 }
270 prepend.push(...mappersImports, ...visitor.globalDeclarations);
271 return {
272 prepend,
273 content: [
274 header,
275 resolversTypeMapping,
276 resolversParentTypeMapping,
277 ...visitorResult.definitions.filter(d => typeof d === 'string'),
278 getRootResolver(),
279 getAllDirectiveResolvers(),
280 ].join('\n'),
281 };
282};
283
284export { TypeScriptResolversVisitor, plugin };
285//# sourceMappingURL=index.esm.js.map