diff --git a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts index 6adb71df597..685c2c44379 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts @@ -1071,54 +1071,58 @@ export class BaseResolversVisitor< memberTypes: readonly GraphQLObjectType[] | GraphQLObjectType[]; isTypenameNonOptional: boolean; }): string { - const result = - memberTypes - .map(type => { - const isTypeMapped = this.config.mappers[type.name]; - // 1. If mapped without placehoder, just use it without doing extra checks - if (isTypeMapped && !hasPlaceholder(isTypeMapped.type)) { - return { typename: type.name, typeValue: isTypeMapped.type }; - } + const members = memberTypes + .map(type => { + const isTypeMapped = this.config.mappers[type.name]; + // 1. If mapped without placehoder, just use it without doing extra checks + if (isTypeMapped && !hasPlaceholder(isTypeMapped.type)) { + return { typename: type.name, typeValue: isTypeMapped.type }; + } - // 2. Work out value for type - // 2a. By default, use the typescript type - let typeValue = this.convertName(type.name, {}, true); + // 2. Work out value for type + // 2a. By default, use the typescript type + let typeValue = this.convertName(type.name, {}, true); - // 2b. Find fields to Omit if needed. - // - If no field to Omit, "type with maybe Omit" is typescript type i.e. no Omit - // - If there are fields to Omit, keep track of these "type with maybe Omit" to replace in original unionMemberValue - const fieldsToOmit = this.getRelevantFieldsToOmit({ - schemaType: type, - getTypeToUse: baseType => `_RefType['${baseType}']`, - }); - if (fieldsToOmit.length > 0) { - typeValue = this.replaceFieldsInType(typeValue, fieldsToOmit); - } + // 2b. Find fields to Omit if needed. + // - If no field to Omit, "type with maybe Omit" is typescript type i.e. no Omit + // - If there are fields to Omit, keep track of these "type with maybe Omit" to replace in original unionMemberValue + const fieldsToOmit = this.getRelevantFieldsToOmit({ + schemaType: type, + getTypeToUse: baseType => `_RefType['${baseType}']`, + }); + if (fieldsToOmit.length > 0) { + typeValue = this.replaceFieldsInType(typeValue, fieldsToOmit); + } - // 2c. If type is mapped with placeholder, use the "type with maybe Omit" as {T} - if (isTypeMapped && hasPlaceholder(isTypeMapped.type)) { - return { typename: type.name, typeValue: replacePlaceholder(isTypeMapped.type, typeValue) }; - } + // 2c. If type is mapped with placeholder, use the "type with maybe Omit" as {T} + if (isTypeMapped && hasPlaceholder(isTypeMapped.type)) { + return { typename: type.name, typeValue: replacePlaceholder(isTypeMapped.type, typeValue) }; + } - // 2d. If has default mapper with placeholder, use the "type with maybe Omit" as {T} - const hasDefaultMapper = !!this.config.defaultMapper?.type; - const isScalar = this.config.scalars[typeName]; - if (hasDefaultMapper && hasPlaceholder(this.config.defaultMapper.type)) { - const finalTypename = isScalar ? this._getScalar(typeName) : typeValue; - return { - typename: type.name, - typeValue: replacePlaceholder(this.config.defaultMapper.type, finalTypename), - }; - } + // 2d. If has default mapper with placeholder, use the "type with maybe Omit" as {T} + const hasDefaultMapper = !!this.config.defaultMapper?.type; + const isScalar = this.config.scalars[typeName]; + if (hasDefaultMapper && hasPlaceholder(this.config.defaultMapper.type)) { + const finalTypename = isScalar ? this._getScalar(typeName) : typeValue; + return { + typename: type.name, + typeValue: replacePlaceholder(this.config.defaultMapper.type, finalTypename), + }; + } - return { typename: type.name, typeValue }; - }) - .map(({ typename, typeValue }) => { - const nonOptionalTypenameModifier = isTypenameNonOptional ? ` & { __typename: '${typename}' }` : ''; + return { typename: type.name, typeValue }; + }) + .map(({ typename, typeValue }) => { + const nonOptionalTypenameModifier = isTypenameNonOptional ? ` & { __typename: '${typename}' }` : ''; - return `( ${typeValue}${nonOptionalTypenameModifier} )`; // Must wrap every type in explicit "( )" to separate them - }) - .join(' | ') || 'never'; + return `( ${typeValue}${nonOptionalTypenameModifier} )`; // Must wrap every type in explicit "( )" to separate them + }); + const result = + members.length === 0 + ? 'never' + : this.config.printFieldsOnNewLines && members.length > 1 + ? `\n | ${members.join('\n | ')}` + : members.join(' | '); return result; } diff --git a/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts b/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts index 34762629067..18b90fdd483 100644 --- a/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts +++ b/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts @@ -833,7 +833,7 @@ export class SelectionSetToObject 1 && this._config.extractAllFieldsToTypes) { return { @@ -933,7 +933,12 @@ export class SelectionSetToObject t.name).join(' | ')).string, + .withContent( + formatUnion( + this._config, + subTypes.map(t => t.name) + ) + ).string, ].join('\n'); } @@ -950,3 +955,10 @@ export class SelectionSetToObject 1) { + return `\n | ${members.join('\n | ')}`; + } + return members.join(' | '); +} diff --git a/packages/plugins/typescript/operations/src/ts-selection-set-processor.ts b/packages/plugins/typescript/operations/src/ts-selection-set-processor.ts index 6de86a0c45d..8f1876466eb 100644 --- a/packages/plugins/typescript/operations/src/ts-selection-set-processor.ts +++ b/packages/plugins/typescript/operations/src/ts-selection-set-processor.ts @@ -25,20 +25,20 @@ export class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor `'${field.fieldName}'`).join(' | ')}>`]; + const escapedFieldNames = fields.map(field => `'${field.fieldName}'`); + return [formattedUnionTransform(this.config, 'MakeEmpty', parentName, escapedFieldNames)]; } let hasConditionals = false; - const conditilnalsList: string[] = []; - let resString = `Pick<${parentName}, ${fields - .map(field => { - if (field.isConditional) { - hasConditionals = true; - conditilnalsList.push(field.fieldName); - } - return `'${field.fieldName}'`; - }) - .join(' | ')}>`; + const escapedConditionalsList: string[] = []; + const escapedFieldNames = fields.map(field => { + if (field.isConditional) { + hasConditionals = true; + escapedConditionalsList.push(`'${field.fieldName}'`); + } + return `'${field.fieldName}'`; + }); + let resString = formattedUnionTransform(this.config, 'Pick', parentName, escapedFieldNames); if (hasConditionals) { const avoidOptional = @@ -52,7 +52,7 @@ export class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor `'${field}'`).join(' | ')}>`; + }${formattedUnionTransform(this.config, transform, resString, escapedConditionalsList)}`; } return [resString]; } @@ -75,18 +75,13 @@ export class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor { - const value = - aliasedField.fieldName === '__typename' - ? `'${schemaType.name}'` - : `${parentName}['${aliasedField.fieldName}']`; - - return `${aliasedField.alias}: ${value}`; - }) - .join(', ')} }`, - ]; + const selections = fields.map(aliasedField => { + const value = + aliasedField.fieldName === '__typename' ? `'${schemaType.name}'` : `${parentName}['${aliasedField.fieldName}']`; + + return `${aliasedField.alias}: ${value}`; + }); + return [formatSelections(this.config, selections)]; } transformLinkFields(fields: LinkField[]): ProcessResult { @@ -94,6 +89,29 @@ export class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor `${field.alias || field.name}: ${field.selectionSet}`).join(', ')} }`]; + const selections = fields.map(field => `${field.alias || field.name}: ${field.selectionSet}`); + + return [formatSelections(this.config, selections)]; + } +} + +/** Equivalent to `${transformName}<${target}, ${unionElements.join(' | ')}>`, but with line feeds if necessary */ +function formattedUnionTransform( + config: SelectionSetProcessorConfig, + transformName: string, + target: string, + unionElements: string[] +): string { + if (config.printFieldsOnNewLines && unionElements.length > 3) { + return `${transformName}<\n ${target},\n | ${unionElements.join('\n | ')}\n >`; + } + return `${transformName}<${target}, ${unionElements.join(' | ')}>`; +} + +/** Equivalent to `{ ${selections.join(', ')} }`, but with line feeds if necessary */ +function formatSelections(config: SelectionSetProcessorConfig, selections: string[]): string { + if (config.printFieldsOnNewLines && selections.length > 1) { + return `{\n ${selections.join(',\n ')},\n }`; } + return `{ ${selections.join(', ')} }`; } diff --git a/packages/plugins/typescript/operations/tests/extract-all-types.spec.ts b/packages/plugins/typescript/operations/tests/extract-all-types.spec.ts index 4389ec63a87..a50a132425e 100644 --- a/packages/plugins/typescript/operations/tests/extract-all-types.spec.ts +++ b/packages/plugins/typescript/operations/tests/extract-all-types.spec.ts @@ -95,7 +95,9 @@ describe('extractAllFieldsToTypes: true', () => { joinDate: any }; - export type UserFragment = UserFragment_DummyUser | UserFragment_ActiveUser; + export type UserFragment = + | UserFragment_DummyUser + | UserFragment_ActiveUser; export type MeFragment_ActiveUser_parentUser_DummyUser = { __typename: 'DummyUser', @@ -109,7 +111,9 @@ describe('extractAllFieldsToTypes: true', () => { joinDate: any }; - export type MeFragment_ActiveUser_parentUser = MeFragment_ActiveUser_parentUser_DummyUser | MeFragment_ActiveUser_parentUser_ActiveUser; + export type MeFragment_ActiveUser_parentUser = + | MeFragment_ActiveUser_parentUser_DummyUser + | MeFragment_ActiveUser_parentUser_ActiveUser; type Me_DummyUser_Fragment = { __typename: 'DummyUser', @@ -125,7 +129,9 @@ describe('extractAllFieldsToTypes: true', () => { parentUser: MeFragment_ActiveUser_parentUser }; - export type MeFragment = Me_DummyUser_Fragment | Me_ActiveUser_Fragment; + export type MeFragment = + | Me_DummyUser_Fragment + | Me_ActiveUser_Fragment; export type OverlappingFieldsMergingTestQuery_me_DummyUser = { __typename: 'DummyUser', @@ -141,7 +147,9 @@ describe('extractAllFieldsToTypes: true', () => { parentUser: MeFragment_ActiveUser_parentUser }; - export type OverlappingFieldsMergingTestQuery_me = OverlappingFieldsMergingTestQuery_me_DummyUser | OverlappingFieldsMergingTestQuery_me_ActiveUser; + export type OverlappingFieldsMergingTestQuery_me = + | OverlappingFieldsMergingTestQuery_me_DummyUser + | OverlappingFieldsMergingTestQuery_me_ActiveUser; export type OverlappingFieldsMergingTestQuery_Query = { __typename: 'Query', @@ -168,7 +176,9 @@ describe('extractAllFieldsToTypes: true', () => { parentUser: MeFragment_ActiveUser_parentUser }; - export type NestedOverlappingFieldsMergingTestQuery_me = NestedOverlappingFieldsMergingTestQuery_me_DummyUser | NestedOverlappingFieldsMergingTestQuery_me_ActiveUser; + export type NestedOverlappingFieldsMergingTestQuery_me = + | NestedOverlappingFieldsMergingTestQuery_me_DummyUser + | NestedOverlappingFieldsMergingTestQuery_me_ActiveUser; export type NestedOverlappingFieldsMergingTestQuery_Query = { __typename: 'Query', @@ -1238,4 +1248,269 @@ describe('extractAllFieldsToTypes: true', () => { await validate(content, config, complexTestSchemaWithUnionsAndInterfaces); }); + + it('should extract types from multiple fragments (preResolveTypes: false, printFieldsOnNewlines: true)', async () => { + const config: TypeScriptDocumentsPluginConfig = { + preResolveTypes: false, + extractAllFieldsToTypes: true, + nonOptionalTypename: true, + dedupeOperationSuffix: true, + printFieldsOnNewLines: true, + }; + const { content } = await plugin( + complexTestSchemaWithUnionsAndInterfaces, + [{ location: 'test-file.ts', document: fragmentsOnComplexSchema }], + config, + { outputFile: '' } + ); + expect(content).toMatchInlineSnapshot(` + "export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = ( + { __typename: 'ArchivedArticle' } + & Pick< + ArchivedArticle, + | 'id' + | 'htmlUrl' + | 'title' + | 'url' + > + ); + + export type ConversationBotSolutionFragment_BotSolution_originatedFrom_EmailInteraction = ( + { __typename: 'EmailInteraction' } + & Pick + ); + + export type ConversationBotSolutionFragment_BotSolution_originatedFrom_CustomChannelInteraction = ( + { __typename: 'CustomChannelInteraction' } + & Pick + ); + + export type ConversationBotSolutionFragment_BotSolution_originatedFrom_TalkInteraction = { __typename: 'TalkInteraction' }; + + export type ConversationBotSolutionFragment_BotSolution_originatedFrom_NativeMessagingInteraction = ( + { __typename: 'NativeMessagingInteraction' } + & Pick + ); + + export type ConversationBotSolutionFragment_BotSolution_originatedFrom_WhatsAppInteraction = ( + { __typename: 'WhatsAppInteraction' } + & Pick + ); + + export type ConversationBotSolutionFragment_BotSolution_originatedFrom_WeChatInteraction = ( + { __typename: 'WeChatInteraction' } + & Pick + ); + + export type ConversationBotSolutionFragment_BotSolution_originatedFrom_NotImplementedOriginatedFrom = { __typename: 'NotImplementedOriginatedFrom' }; + + export type ConversationBotSolutionFragment_BotSolution_originatedFrom = + | ConversationBotSolutionFragment_BotSolution_originatedFrom_EmailInteraction + | ConversationBotSolutionFragment_BotSolution_originatedFrom_CustomChannelInteraction + | ConversationBotSolutionFragment_BotSolution_originatedFrom_TalkInteraction + | ConversationBotSolutionFragment_BotSolution_originatedFrom_NativeMessagingInteraction + | ConversationBotSolutionFragment_BotSolution_originatedFrom_WhatsAppInteraction + | ConversationBotSolutionFragment_BotSolution_originatedFrom_WeChatInteraction + | ConversationBotSolutionFragment_BotSolution_originatedFrom_NotImplementedOriginatedFrom; + + export type ConversationBotSolutionFragment = ( + { __typename: 'BotSolution' } + & Pick + & { + article: ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle, + originatedFrom: ConversationBotSolutionFragment_BotSolution_originatedFrom, + } + ); + + export type ConversationGenericCallSummaryFragment = ( + { __typename: 'TalkPublicCallSummary' } + & Pick + ); + + export type ConversationTalkInteractionFragment = ( + { __typename: 'TalkInteraction' } + & Pick + ); + + export type ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_EmailInteraction = ( + { __typename: 'EmailInteraction' } + & Pick + ); + + export type ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_CustomChannelInteraction = ( + { __typename: 'CustomChannelInteraction' } + & Pick + ); + + export type ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_TalkInteraction = { __typename: 'TalkInteraction' }; + + export type ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_NativeMessagingInteraction = ( + { __typename: 'NativeMessagingInteraction' } + & Pick + ); + + export type ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_WhatsAppInteraction = ( + { __typename: 'WhatsAppInteraction' } + & Pick + ); + + export type ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_WeChatInteraction = ( + { __typename: 'WeChatInteraction' } + & Pick + ); + + export type ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_NotImplementedOriginatedFrom = { __typename: 'NotImplementedOriginatedFrom' }; + + export type ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom = + | ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_EmailInteraction + | ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_CustomChannelInteraction + | ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_TalkInteraction + | ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_NativeMessagingInteraction + | ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_WhatsAppInteraction + | ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_WeChatInteraction + | ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom_NotImplementedOriginatedFrom; + + type ConversationConversationEvent_BrokenConversationEvent_Fragment = ( + { __typename: 'BrokenConversationEvent' } + & Pick + & { originatedFrom: ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom } + ); + + type ConversationConversationEvent_BotSolution_Fragment = ( + { __typename: 'BotSolution' } + & Pick + & { originatedFrom: ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom } + ); + + type ConversationConversationEvent_TalkPublicCallSummary_Fragment = ( + { __typename: 'TalkPublicCallSummary' } + & Pick + & { originatedFrom: ConversationConversationEventFragment_BrokenConversationEvent_originatedFrom } + ); + + export type ConversationConversationEventFragment = + | ConversationConversationEvent_BrokenConversationEvent_Fragment + | ConversationConversationEvent_BotSolution_Fragment + | ConversationConversationEvent_TalkPublicCallSummary_Fragment; + + type MessageEnvelopeData_EmailInteraction_Fragment = ( + { __typename: 'EmailInteraction' } + & Pick + ); + + type MessageEnvelopeData_CustomChannelInteraction_Fragment = { __typename: 'CustomChannelInteraction' }; + + type MessageEnvelopeData_TalkInteraction_Fragment = { __typename: 'TalkInteraction' }; + + type MessageEnvelopeData_NativeMessagingInteraction_Fragment = { __typename: 'NativeMessagingInteraction' }; + + type MessageEnvelopeData_WhatsAppInteraction_Fragment = { __typename: 'WhatsAppInteraction' }; + + type MessageEnvelopeData_WeChatInteraction_Fragment = { __typename: 'WeChatInteraction' }; + + type MessageEnvelopeData_NotImplementedOriginatedFrom_Fragment = { __typename: 'NotImplementedOriginatedFrom' }; + + export type MessageEnvelopeDataFragment = + | MessageEnvelopeData_EmailInteraction_Fragment + | MessageEnvelopeData_CustomChannelInteraction_Fragment + | MessageEnvelopeData_TalkInteraction_Fragment + | MessageEnvelopeData_NativeMessagingInteraction_Fragment + | MessageEnvelopeData_WhatsAppInteraction_Fragment + | MessageEnvelopeData_WeChatInteraction_Fragment + | MessageEnvelopeData_NotImplementedOriginatedFrom_Fragment; + + export type AnyChannelOriginatedFromFragment = ( + { __typename: 'CustomChannelInteraction' } + & Pick + ); + + type ConversationOriginatedFrom_EmailInteraction_Fragment = ( + { __typename: 'EmailInteraction' } + & Pick + ); + + type ConversationOriginatedFrom_CustomChannelInteraction_Fragment = ( + { __typename: 'CustomChannelInteraction' } + & Pick + ); + + type ConversationOriginatedFrom_TalkInteraction_Fragment = { __typename: 'TalkInteraction' }; + + type ConversationOriginatedFrom_NativeMessagingInteraction_Fragment = ( + { __typename: 'NativeMessagingInteraction' } + & Pick + ); + + type ConversationOriginatedFrom_WhatsAppInteraction_Fragment = ( + { __typename: 'WhatsAppInteraction' } + & Pick + ); + + type ConversationOriginatedFrom_WeChatInteraction_Fragment = ( + { __typename: 'WeChatInteraction' } + & Pick + ); + + type ConversationOriginatedFrom_NotImplementedOriginatedFrom_Fragment = { __typename: 'NotImplementedOriginatedFrom' }; + + export type ConversationOriginatedFromFragment = + | ConversationOriginatedFrom_EmailInteraction_Fragment + | ConversationOriginatedFrom_CustomChannelInteraction_Fragment + | ConversationOriginatedFrom_TalkInteraction_Fragment + | ConversationOriginatedFrom_NativeMessagingInteraction_Fragment + | ConversationOriginatedFrom_WhatsAppInteraction_Fragment + | ConversationOriginatedFrom_WeChatInteraction_Fragment + | ConversationOriginatedFrom_NotImplementedOriginatedFrom_Fragment; + + export type ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_EmailInteraction = ( + { __typename: 'EmailInteraction' } + & Pick + ); + + export type ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_CustomChannelInteraction = ( + { __typename: 'CustomChannelInteraction' } + & Pick + ); + + export type ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_TalkInteraction = ( + { __typename: 'TalkInteraction' } + & Pick + ); + + export type ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_NativeMessagingInteraction = ( + { __typename: 'NativeMessagingInteraction' } + & Pick + ); + + export type ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_WhatsAppInteraction = ( + { __typename: 'WhatsAppInteraction' } + & Pick + ); + + export type ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_WeChatInteraction = ( + { __typename: 'WeChatInteraction' } + & Pick + ); + + export type ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_NotImplementedOriginatedFrom = { __typename: 'NotImplementedOriginatedFrom' }; + + export type ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom = + | ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_EmailInteraction + | ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_CustomChannelInteraction + | ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_TalkInteraction + | ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_NativeMessagingInteraction + | ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_WhatsAppInteraction + | ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_WeChatInteraction + | ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom_NotImplementedOriginatedFrom; + + export type ConversationTalkPublicCallSummaryFragment = ( + { __typename: 'TalkPublicCallSummary' } + & Pick + & { originatedFrom: ConversationTalkPublicCallSummaryFragment_TalkPublicCallSummary_originatedFrom } + ); + " + `); + + await validate(content, config, complexTestSchemaWithUnionsAndInterfaces); + }); }); diff --git a/packages/plugins/typescript/resolvers/tests/__snapshots__/ts-resolvers.spec.ts.snap b/packages/plugins/typescript/resolvers/tests/__snapshots__/ts-resolvers.spec.ts.snap index 8783aac2cb1..f73abad6425 100644 --- a/packages/plugins/typescript/resolvers/tests/__snapshots__/ts-resolvers.spec.ts.snap +++ b/packages/plugins/typescript/resolvers/tests/__snapshots__/ts-resolvers.spec.ts.snap @@ -353,6 +353,265 @@ export type DirectiveResolvers = ResolversObject<{ " `; +exports[`TypeScript Resolvers Plugin Config namespacedImportName - should work correctly with imported namespaced type (printFieldsOnNewLines: true) 1`] = ` +"import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; +export type Omit = Pick>; +export type RequireFields = Omit & { [P in K]-?: NonNullable }; +export type WithIndex = TObject & Record; +export type ResolversObject = WithIndex; + +export type ResolverTypeWrapper = Promise | T; + + +export type ResolverWithResolve = { + resolve: ResolverFn; +}; +export type Resolver = ResolverFn | ResolverWithResolve; + +export type ResolverFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => Promise | TResult; + +export type SubscriptionSubscribeFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => AsyncIterable | Promise>; + +export type SubscriptionResolveFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + +export interface SubscriptionSubscriberObject { + subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; + resolve?: SubscriptionResolveFn; +} + +export interface SubscriptionResolverObject { + subscribe: SubscriptionSubscribeFn; + resolve: SubscriptionResolveFn; +} + +export type SubscriptionObject = + | SubscriptionSubscriberObject + | SubscriptionResolverObject; + +export type SubscriptionResolver = + | ((...args: any[]) => SubscriptionObject) + | SubscriptionObject; + +export type TypeResolveFn = ( + parent: TParent, + context: TContext, + info: GraphQLResolveInfo +) => Types.Maybe | Promise>; + +export type IsTypeOfResolverFn = (obj: T, context: TContext, info: GraphQLResolveInfo) => boolean | Promise; + +export type NextResolverFn = () => Promise; + +export type DirectiveResolverFn = ( + next: NextResolverFn, + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + +/** Mapping of union types */ +export type ResolversUnionTypes<_RefType extends Record> = ResolversObject<{ + ChildUnion: + | ( Omit & { parent?: Types.Maybe<_RefType['MyType']> } ) + | ( Types.MyOtherType ); + MyUnion: + | ( Omit & { unionChild?: Types.Maybe<_RefType['ChildUnion']> } ) + | ( Types.MyOtherType ); +}>; + +/** Mapping of interface types */ +export type ResolversInterfaceTypes<_RefType extends Record> = ResolversObject<{ + Node: ( Types.SomeNode ); + AnotherNode: + | ( Omit & { unionChild?: Types.Maybe<_RefType['ChildUnion']>, interfaceChild?: Types.Maybe<_RefType['Node']> } ) + | ( Omit & { unionChild?: Types.Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Types.Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } ); + WithChild: + | ( Omit & { unionChild?: Types.Maybe<_RefType['ChildUnion']>, interfaceChild?: Types.Maybe<_RefType['Node']> } ) + | ( Omit & { unionChild?: Types.Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Types.Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } ); + WithChildren: ( Omit & { unionChild?: Types.Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Types.Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } ); +}>; + +/** Mapping between all available schema types and the resolvers types */ +export type ResolversTypes = ResolversObject<{ + MyType: ResolverTypeWrapper & { unionChild?: Types.Maybe }>; + String: ResolverTypeWrapper; + Child: ResolverTypeWrapper & { parent?: Types.Maybe }>; + MyOtherType: ResolverTypeWrapper; + ChildUnion: ResolverTypeWrapper['ChildUnion']>; + Query: ResolverTypeWrapper<{}>; + Subscription: ResolverTypeWrapper<{}>; + Node: ResolverTypeWrapper['Node']>; + ID: ResolverTypeWrapper; + SomeNode: ResolverTypeWrapper; + AnotherNode: ResolverTypeWrapper['AnotherNode']>; + WithChild: ResolverTypeWrapper['WithChild']>; + WithChildren: ResolverTypeWrapper['WithChildren']>; + AnotherNodeWithChild: ResolverTypeWrapper & { unionChild?: Types.Maybe, interfaceChild?: Types.Maybe }>; + AnotherNodeWithAll: ResolverTypeWrapper & { unionChild?: Types.Maybe, unionChildren: Array, interfaceChild?: Types.Maybe, interfaceChildren: Array }>; + MyUnion: ResolverTypeWrapper['MyUnion']>; + MyScalar: ResolverTypeWrapper; + Int: ResolverTypeWrapper; + Boolean: ResolverTypeWrapper; +}>; + +/** Mapping between all available schema types and the resolvers parents */ +export type ResolversParentTypes = ResolversObject<{ + MyType: Omit & { unionChild?: Types.Maybe }; + String: Types.Scalars['String']['output']; + Child: Omit & { parent?: Types.Maybe }; + MyOtherType: Types.MyOtherType; + ChildUnion: ResolversUnionTypes['ChildUnion']; + Query: {}; + Subscription: {}; + Node: ResolversInterfaceTypes['Node']; + ID: Types.Scalars['ID']['output']; + SomeNode: Types.SomeNode; + AnotherNode: ResolversInterfaceTypes['AnotherNode']; + WithChild: ResolversInterfaceTypes['WithChild']; + WithChildren: ResolversInterfaceTypes['WithChildren']; + AnotherNodeWithChild: Omit & { unionChild?: Types.Maybe, interfaceChild?: Types.Maybe }; + AnotherNodeWithAll: Omit & { unionChild?: Types.Maybe, unionChildren: Array, interfaceChild?: Types.Maybe, interfaceChildren: Array }; + MyUnion: ResolversUnionTypes['MyUnion']; + MyScalar: Types.Scalars['MyScalar']['output']; + Int: Types.Scalars['Int']['output']; + Boolean: Types.Scalars['Boolean']['output']; +}>; + +export type MyDirectiveDirectiveArgs = { + arg: Types.Scalars['Int']['input']; + arg2: Types.Scalars['String']['input']; + arg3: Types.Scalars['Boolean']['input']; +}; + +export type MyDirectiveDirectiveResolver = DirectiveResolverFn; + +export type AuthenticatedDirectiveArgs = { }; + +export type AuthenticatedDirectiveResolver = DirectiveResolverFn; + +export type MyTypeResolvers = ResolversObject<{ + foo?: Resolver; + otherType?: Resolver, ParentType, ContextType>; + withArgs?: Resolver, ParentType, ContextType, RequireFields>; + unionChild?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type ChildResolvers = ResolversObject<{ + bar?: Resolver; + parent?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type MyOtherTypeResolvers = ResolversObject<{ + bar?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type ChildUnionResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'Child' | 'MyOtherType', ParentType, ContextType>; +}>; + +export type QueryResolvers = ResolversObject<{ + something?: Resolver; +}>; + +export type SubscriptionResolvers = ResolversObject<{ + somethingChanged?: SubscriptionResolver, "somethingChanged", ParentType, ContextType>; +}>; + +export type NodeResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'SomeNode', ParentType, ContextType>; + id?: Resolver; +}>; + +export type SomeNodeResolvers = ResolversObject<{ + id?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type AnotherNodeResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'AnotherNodeWithChild' | 'AnotherNodeWithAll', ParentType, ContextType>; + id?: Resolver; +}>; + +export type WithChildResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'AnotherNodeWithChild' | 'AnotherNodeWithAll', ParentType, ContextType>; + unionChild?: Resolver, ParentType, ContextType>; + node?: Resolver, ParentType, ContextType>; +}>; + +export type WithChildrenResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'AnotherNodeWithAll', ParentType, ContextType>; + unionChildren?: Resolver, ParentType, ContextType>; + nodes?: Resolver, ParentType, ContextType>; +}>; + +export type AnotherNodeWithChildResolvers = ResolversObject<{ + id?: Resolver; + unionChild?: Resolver, ParentType, ContextType>; + interfaceChild?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type AnotherNodeWithAllResolvers = ResolversObject<{ + id?: Resolver; + unionChild?: Resolver, ParentType, ContextType>; + unionChildren?: Resolver, ParentType, ContextType>; + interfaceChild?: Resolver, ParentType, ContextType>; + interfaceChildren?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type MyUnionResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'MyType' | 'MyOtherType', ParentType, ContextType>; +}>; + +export interface MyScalarScalarConfig extends GraphQLScalarTypeConfig { + name: 'MyScalar'; +} + +export type Resolvers = ResolversObject<{ + MyType?: MyTypeResolvers; + Child?: ChildResolvers; + MyOtherType?: MyOtherTypeResolvers; + ChildUnion?: ChildUnionResolvers; + Query?: QueryResolvers; + Subscription?: SubscriptionResolvers; + Node?: NodeResolvers; + SomeNode?: SomeNodeResolvers; + AnotherNode?: AnotherNodeResolvers; + WithChild?: WithChildResolvers; + WithChildren?: WithChildrenResolvers; + AnotherNodeWithChild?: AnotherNodeWithChildResolvers; + AnotherNodeWithAll?: AnotherNodeWithAllResolvers; + MyUnion?: MyUnionResolvers; + MyScalar?: GraphQLScalarType; +}>; + +export type DirectiveResolvers = ResolversObject<{ + myDirective?: MyDirectiveDirectiveResolver; + authenticated?: AuthenticatedDirectiveResolver; +}>; +" +`; + exports[`TypeScript Resolvers Plugin Config namespacedImportName - should work correctly with imported namespaced type 1`] = ` "import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; export type Omit = Pick>; diff --git a/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts b/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts index a1d13fe829e..caf7d1876e4 100644 --- a/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts +++ b/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts @@ -152,6 +152,18 @@ describe('TypeScript Resolvers Plugin', () => { expect(content).toMatchSnapshot(); }); + it('namespacedImportName - should work correctly with imported namespaced type (printFieldsOnNewLines: true)', async () => { + const config = { + noSchemaStitching: true, + useIndexSignature: true, + namespacedImportName: 'Types', + printFieldsOnNewLines: true, + }; + const result = await plugin(resolversTestingSchema, [], config, { outputFile: '' }); + const content = mergeOutputs([result]); + expect(content).toMatchSnapshot(); + }); + it('directiveResolverMappings - should generate correct types (inline definition)', async () => { const config = { noSchemaStitching: true,