diff --git a/.changeset/clever-loops-crash.md b/.changeset/clever-loops-crash.md new file mode 100644 index 00000000000..c28b7d69057 --- /dev/null +++ b/.changeset/clever-loops-crash.md @@ -0,0 +1,6 @@ +--- +'@graphql-codegen/visitor-plugin-common': minor +'@graphql-codegen/typescript-operations': minor +--- + +Add importSchemaTypesFrom support diff --git a/dev-test/codegen.ts b/dev-test/codegen.ts index 04584a36395..a1126bf169e 100644 --- a/dev-test/codegen.ts +++ b/dev-test/codegen.ts @@ -256,6 +256,25 @@ const config: CodegenConfig = { }, }, }, + + // standalone-operations + './dev-test/standalone-operations/import-schema-types/_base.generated.ts': { + schema: './dev-test/standalone-operations/schema.graphql', + documents: ['./dev-test/standalone-operations/import-schema-types/*.graphql'], + plugins: ['typescript-operations'], + config: { + generatesOperationTypes: false, + }, + }, + './dev-test/standalone-operations/import-schema-types/_types.generated.ts': { + schema: './dev-test/standalone-operations/schema.graphql', + documents: ['./dev-test/standalone-operations/import-schema-types/*.graphql'], + plugins: ['typescript-operations'], + config: { + importSchemaTypesFrom: './dev-test/standalone-operations/import-schema-types/_base.generated.ts', + namespacedImportName: 'Types', + }, + }, }, }; diff --git a/dev-test/githunt/typed-document-nodes.ts b/dev-test/githunt/typed-document-nodes.ts index 1de574e175e..4eed5751e4f 100644 --- a/dev-test/githunt/typed-document-nodes.ts +++ b/dev-test/githunt/typed-document-nodes.ts @@ -169,18 +169,6 @@ export enum VoteType { Up = 'UP', } -/** A list of options for the sort order of the feed */ -export type FeedType = - /** Sort by a combination of freshness and score, using Reddit's algorithm */ - | 'HOT' - /** Newest entries first */ - | 'NEW' - /** Highest score entries first */ - | 'TOP'; - -/** The type of vote to record, when submitting a vote */ -export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; - export type OnCommentAddedSubscriptionVariables = Exact<{ repoFullName: string; }>; diff --git a/dev-test/githunt/types.avoidOptionals.ts b/dev-test/githunt/types.avoidOptionals.ts index 0d19fae050d..66c403d7758 100644 --- a/dev-test/githunt/types.avoidOptionals.ts +++ b/dev-test/githunt/types.avoidOptionals.ts @@ -168,18 +168,6 @@ export enum VoteType { Up = 'UP', } -/** A list of options for the sort order of the feed */ -export type FeedType = - /** Sort by a combination of freshness and score, using Reddit's algorithm */ - | 'HOT' - /** Newest entries first */ - | 'NEW' - /** Highest score entries first */ - | 'TOP'; - -/** The type of vote to record, when submitting a vote */ -export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; - export type OnCommentAddedSubscriptionVariables = Exact<{ repoFullName: string; }>; diff --git a/dev-test/githunt/types.d.ts b/dev-test/githunt/types.d.ts index cf2dc2f2014..b84825f3808 100644 --- a/dev-test/githunt/types.d.ts +++ b/dev-test/githunt/types.d.ts @@ -163,18 +163,6 @@ export type Vote = { /** The type of vote to record, when submitting a vote */ export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; -/** A list of options for the sort order of the feed */ -export type FeedType = - /** Sort by a combination of freshness and score, using Reddit's algorithm */ - | 'HOT' - /** Newest entries first */ - | 'NEW' - /** Highest score entries first */ - | 'TOP'; - -/** The type of vote to record, when submitting a vote */ -export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; - export type OnCommentAddedSubscriptionVariables = Exact<{ repoFullName: string; }>; diff --git a/dev-test/githunt/types.enumsAsTypes.ts b/dev-test/githunt/types.enumsAsTypes.ts index cf2dc2f2014..b84825f3808 100644 --- a/dev-test/githunt/types.enumsAsTypes.ts +++ b/dev-test/githunt/types.enumsAsTypes.ts @@ -163,18 +163,6 @@ export type Vote = { /** The type of vote to record, when submitting a vote */ export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; -/** A list of options for the sort order of the feed */ -export type FeedType = - /** Sort by a combination of freshness and score, using Reddit's algorithm */ - | 'HOT' - /** Newest entries first */ - | 'NEW' - /** Highest score entries first */ - | 'TOP'; - -/** The type of vote to record, when submitting a vote */ -export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; - export type OnCommentAddedSubscriptionVariables = Exact<{ repoFullName: string; }>; diff --git a/dev-test/githunt/types.flatten.preResolveTypes.ts b/dev-test/githunt/types.flatten.preResolveTypes.ts index d41d3fc8780..90348f33215 100644 --- a/dev-test/githunt/types.flatten.preResolveTypes.ts +++ b/dev-test/githunt/types.flatten.preResolveTypes.ts @@ -168,18 +168,6 @@ export enum VoteType { Up = 'UP', } -/** A list of options for the sort order of the feed */ -export type FeedType = - /** Sort by a combination of freshness and score, using Reddit's algorithm */ - | 'HOT' - /** Newest entries first */ - | 'NEW' - /** Highest score entries first */ - | 'TOP'; - -/** The type of vote to record, when submitting a vote */ -export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; - export type OnCommentAddedSubscriptionVariables = Exact<{ repoFullName: string; }>; diff --git a/dev-test/githunt/types.immutableTypes.ts b/dev-test/githunt/types.immutableTypes.ts index 42cf26e9d56..f708b3c3dce 100644 --- a/dev-test/githunt/types.immutableTypes.ts +++ b/dev-test/githunt/types.immutableTypes.ts @@ -168,18 +168,6 @@ export enum VoteType { Up = 'UP', } -/** A list of options for the sort order of the feed */ -export type FeedType = - /** Sort by a combination of freshness and score, using Reddit's algorithm */ - | 'HOT' - /** Newest entries first */ - | 'NEW' - /** Highest score entries first */ - | 'TOP'; - -/** The type of vote to record, when submitting a vote */ -export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; - export type OnCommentAddedSubscriptionVariables = Exact<{ repoFullName: string; }>; diff --git a/dev-test/githunt/types.preResolveTypes.onlyOperationTypes.ts b/dev-test/githunt/types.preResolveTypes.onlyOperationTypes.ts index e54e8ab576f..29bff30b9dd 100644 --- a/dev-test/githunt/types.preResolveTypes.onlyOperationTypes.ts +++ b/dev-test/githunt/types.preResolveTypes.onlyOperationTypes.ts @@ -31,18 +31,6 @@ export enum VoteType { Up = 'UP', } -/** A list of options for the sort order of the feed */ -export type FeedType = - /** Sort by a combination of freshness and score, using Reddit's algorithm */ - | 'HOT' - /** Newest entries first */ - | 'NEW' - /** Highest score entries first */ - | 'TOP'; - -/** The type of vote to record, when submitting a vote */ -export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; - export type OnCommentAddedSubscriptionVariables = Exact<{ repoFullName: string; }>; diff --git a/dev-test/githunt/types.preResolveTypes.ts b/dev-test/githunt/types.preResolveTypes.ts index a6114cf2411..c63166be345 100644 --- a/dev-test/githunt/types.preResolveTypes.ts +++ b/dev-test/githunt/types.preResolveTypes.ts @@ -168,18 +168,6 @@ export enum VoteType { Up = 'UP', } -/** A list of options for the sort order of the feed */ -export type FeedType = - /** Sort by a combination of freshness and score, using Reddit's algorithm */ - | 'HOT' - /** Newest entries first */ - | 'NEW' - /** Highest score entries first */ - | 'TOP'; - -/** The type of vote to record, when submitting a vote */ -export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; - export type OnCommentAddedSubscriptionVariables = Exact<{ repoFullName: string; }>; diff --git a/dev-test/githunt/types.ts b/dev-test/githunt/types.ts index a6114cf2411..c63166be345 100644 --- a/dev-test/githunt/types.ts +++ b/dev-test/githunt/types.ts @@ -168,18 +168,6 @@ export enum VoteType { Up = 'UP', } -/** A list of options for the sort order of the feed */ -export type FeedType = - /** Sort by a combination of freshness and score, using Reddit's algorithm */ - | 'HOT' - /** Newest entries first */ - | 'NEW' - /** Highest score entries first */ - | 'TOP'; - -/** The type of vote to record, when submitting a vote */ -export type VoteType = 'CANCEL' | 'DOWN' | 'UP'; - export type OnCommentAddedSubscriptionVariables = Exact<{ repoFullName: string; }>; diff --git a/dev-test/standalone-operations/import-schema-types/_base.generated.ts b/dev-test/standalone-operations/import-schema-types/_base.generated.ts new file mode 100644 index 00000000000..8df53ac5f67 --- /dev/null +++ b/dev-test/standalone-operations/import-schema-types/_base.generated.ts @@ -0,0 +1,6 @@ +/** UserRole Description */ +export type UserRole = + /** UserRole ADMIN */ + | 'ADMIN' + /** UserRole CUSTOMER */ + | 'CUSTOMER'; diff --git a/dev-test/standalone-operations/import-schema-types/_types.generated.ts b/dev-test/standalone-operations/import-schema-types/_types.generated.ts new file mode 100644 index 00000000000..f51c326a287 --- /dev/null +++ b/dev-test/standalone-operations/import-schema-types/_types.generated.ts @@ -0,0 +1,11 @@ +import type * as Types from './_base.generated'; + +type Exact = { [K in keyof T]: T[K] }; +export type WithVariablesQueryVariables = Exact<{ + role?: Types.UserRole | null; +}>; + +export type WithVariablesQuery = { + __typename?: 'Query'; + user?: { __typename?: 'User'; id: string; name: string } | null; +}; diff --git a/dev-test/standalone-operations/import-schema-types/query.graphql b/dev-test/standalone-operations/import-schema-types/query.graphql new file mode 100644 index 00000000000..e8de0b4506d --- /dev/null +++ b/dev-test/standalone-operations/import-schema-types/query.graphql @@ -0,0 +1,6 @@ +query WithVariables($role: UserRole) { + user(id: "100") { + id + name + } +} diff --git a/dev-test/standalone-operations/schema.graphql b/dev-test/standalone-operations/schema.graphql new file mode 100644 index 00000000000..2fd2cb04bc6 --- /dev/null +++ b/dev-test/standalone-operations/schema.graphql @@ -0,0 +1,17 @@ +type Query { + user(id: ID!, role: UserRole): User +} + +type User { + id: ID! + name: String! + role: UserRole! +} + +"UserRole Description" +enum UserRole { + "UserRole ADMIN" + ADMIN + "UserRole CUSTOMER" + CUSTOMER +} diff --git a/dev-test/star-wars/types.avoidOptionals.ts b/dev-test/star-wars/types.avoidOptionals.ts index 96a96ea0b4d..24b23e24f0c 100644 --- a/dev-test/star-wars/types.avoidOptionals.ts +++ b/dev-test/star-wars/types.avoidOptionals.ts @@ -240,15 +240,6 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; -/** The episodes in the Star Wars trilogy */ -export type Episode = - /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ - | 'EMPIRE' - /** Star Wars Episode VI: Return of the Jedi, released in 1983. */ - | 'JEDI' - /** Star Wars Episode IV: A New Hope, released in 1977. */ - | 'NEWHOPE'; - export type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/dev-test/star-wars/types.excludeQueryAlpha.ts b/dev-test/star-wars/types.excludeQueryAlpha.ts index 7ccbc0a787c..40703b13094 100644 --- a/dev-test/star-wars/types.excludeQueryAlpha.ts +++ b/dev-test/star-wars/types.excludeQueryAlpha.ts @@ -240,15 +240,6 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; -/** The episodes in the Star Wars trilogy */ -export type Episode = - /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ - | 'EMPIRE' - /** Star Wars Episode VI: Return of the Jedi, released in 1983. */ - | 'JEDI' - /** Star Wars Episode IV: A New Hope, released in 1977. */ - | 'NEWHOPE'; - export type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/dev-test/star-wars/types.excludeQueryBeta.ts b/dev-test/star-wars/types.excludeQueryBeta.ts index 9f20261977e..5b38889e9ac 100644 --- a/dev-test/star-wars/types.excludeQueryBeta.ts +++ b/dev-test/star-wars/types.excludeQueryBeta.ts @@ -240,15 +240,6 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; -/** The episodes in the Star Wars trilogy */ -export type Episode = - /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ - | 'EMPIRE' - /** Star Wars Episode VI: Return of the Jedi, released in 1983. */ - | 'JEDI' - /** Star Wars Episode IV: A New Hope, released in 1977. */ - | 'NEWHOPE'; - export type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/dev-test/star-wars/types.globallyAvailable.d.ts b/dev-test/star-wars/types.globallyAvailable.d.ts index 0e376af86bc..2efbb00482e 100644 --- a/dev-test/star-wars/types.globallyAvailable.d.ts +++ b/dev-test/star-wars/types.globallyAvailable.d.ts @@ -238,15 +238,6 @@ type StarshipLengthArgs = { unit?: InputMaybe; }; -/** The episodes in the Star Wars trilogy */ -type Episode = - /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ - | 'EMPIRE' - /** Star Wars Episode VI: Return of the Jedi, released in 1983. */ - | 'JEDI' - /** Star Wars Episode IV: A New Hope, released in 1977. */ - | 'NEWHOPE'; - type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/dev-test/star-wars/types.immutableTypes.ts b/dev-test/star-wars/types.immutableTypes.ts index bb485fff875..5849e8f6d04 100644 --- a/dev-test/star-wars/types.immutableTypes.ts +++ b/dev-test/star-wars/types.immutableTypes.ts @@ -240,15 +240,6 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; -/** The episodes in the Star Wars trilogy */ -export type Episode = - /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ - | 'EMPIRE' - /** Star Wars Episode VI: Return of the Jedi, released in 1983. */ - | 'JEDI' - /** Star Wars Episode IV: A New Hope, released in 1977. */ - | 'NEWHOPE'; - export type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts b/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts index 9597c939d8f..d5430785c67 100644 --- a/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts +++ b/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts @@ -49,15 +49,6 @@ export type ReviewInput = { stars: Scalars['Int']['input']; }; -/** The episodes in the Star Wars trilogy */ -export type Episode = - /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ - | 'EMPIRE' - /** Star Wars Episode VI: Return of the Jedi, released in 1983. */ - | 'JEDI' - /** Star Wars Episode IV: A New Hope, released in 1977. */ - | 'NEWHOPE'; - export type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/dev-test/star-wars/types.preResolveTypes.ts b/dev-test/star-wars/types.preResolveTypes.ts index 99c11f7e757..5e82a72b7e0 100644 --- a/dev-test/star-wars/types.preResolveTypes.ts +++ b/dev-test/star-wars/types.preResolveTypes.ts @@ -240,15 +240,6 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; -/** The episodes in the Star Wars trilogy */ -export type Episode = - /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ - | 'EMPIRE' - /** Star Wars Episode VI: Return of the Jedi, released in 1983. */ - | 'JEDI' - /** Star Wars Episode IV: A New Hope, released in 1977. */ - | 'NEWHOPE'; - export type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/dev-test/star-wars/types.skipSchema.ts b/dev-test/star-wars/types.skipSchema.ts index 99c11f7e757..5e82a72b7e0 100644 --- a/dev-test/star-wars/types.skipSchema.ts +++ b/dev-test/star-wars/types.skipSchema.ts @@ -240,15 +240,6 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; -/** The episodes in the Star Wars trilogy */ -export type Episode = - /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ - | 'EMPIRE' - /** Star Wars Episode VI: Return of the Jedi, released in 1983. */ - | 'JEDI' - /** Star Wars Episode IV: A New Hope, released in 1977. */ - | 'NEWHOPE'; - export type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/dev-test/star-wars/types.ts b/dev-test/star-wars/types.ts index 99c11f7e757..5e82a72b7e0 100644 --- a/dev-test/star-wars/types.ts +++ b/dev-test/star-wars/types.ts @@ -240,15 +240,6 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; -/** The episodes in the Star Wars trilogy */ -export type Episode = - /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ - | 'EMPIRE' - /** Star Wars Episode VI: Return of the Jedi, released in 1983. */ - | 'JEDI' - /** Star Wars Episode IV: A New Hope, released in 1977. */ - | 'NEWHOPE'; - export type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts index 8ef3b117e88..88f1cb6bf82 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts @@ -42,6 +42,7 @@ export interface ParsedDocumentsConfig extends ParsedTypesConfig { mergeFragmentTypes: boolean; customDirectives: CustomDirectivesConfig; generatesOperationTypes: boolean; + importSchemaTypesFrom: string; } export interface RawDocumentsConfig extends RawTypesConfig { @@ -179,6 +180,7 @@ export interface RawDocumentsConfig extends RawTypesConfig { /** * @description Whether to generate operation types such as Variables, Query/Mutation/Subscription selection set, and Fragment types + * This can be used with `importSchemaTypesFrom` to generate shared used Enums and Input. * @default true * @exampleMarkdown * ```ts filename="codegen.ts" @@ -199,6 +201,30 @@ export interface RawDocumentsConfig extends RawTypesConfig { * ``` */ generatesOperationTypes?: boolean; + + /** + * @description The absolute (prefixed with `~`) or relative path from `cwd` to the shared used Enums and Input (See `generatesOperationTypes`). + * @default true + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript-operations'], + * config: { + * importSchemaTypesFrom: './path/to/shared-types.ts', // relative + * importSchemaTypesFrom: '~@my-org/package' // absolute + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + importSchemaTypesFrom?: string; } export class BaseDocumentsVisitor< @@ -232,6 +258,7 @@ export class BaseDocumentsVisitor< scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars), customDirectives: getConfigValue(rawConfig.customDirectives, { apolloUnmask: false }), generatesOperationTypes: getConfigValue(rawConfig.generatesOperationTypes, true), + importSchemaTypesFrom: getConfigValue(rawConfig.importSchemaTypesFrom, ''), ...((additionalConfig || {}) as any), }); diff --git a/packages/plugins/other/visitor-plugin-common/src/graphql-type-utils.ts b/packages/plugins/other/visitor-plugin-common/src/graphql-type-utils.ts new file mode 100644 index 00000000000..82634cd2849 --- /dev/null +++ b/packages/plugins/other/visitor-plugin-common/src/graphql-type-utils.ts @@ -0,0 +1,13 @@ +import { type GraphQLNamedType, isIntrospectionType, isSpecifiedScalarType } from 'graphql'; + +export const isNativeNamedType = (namedType: GraphQLNamedType): boolean => { + // "Native" NamedType in this context means the following: + // 1. introspection types i.e. with `__` prefixes + // 2. base scalars e.g. Boolean, Int, etc. + // 3. Other natives (mostly base scalars) which was not defined in the schema i.e. no `astNode` + if (isSpecifiedScalarType(namedType) || isIntrospectionType(namedType) || !namedType.astNode) { + return true; + } + + return false; +}; diff --git a/packages/plugins/other/visitor-plugin-common/src/index.ts b/packages/plugins/other/visitor-plugin-common/src/index.ts index bdb72829a17..f645aa973c3 100644 --- a/packages/plugins/other/visitor-plugin-common/src/index.ts +++ b/packages/plugins/other/visitor-plugin-common/src/index.ts @@ -18,3 +18,4 @@ export * from './types.js'; export * from './utils.js'; export * from './variables-to-object.js'; export * from './convert-schema-enum-to-declaration-block-string.js'; +export * from './graphql-type-utils.js'; diff --git a/packages/plugins/typescript/operations/src/index.ts b/packages/plugins/typescript/operations/src/index.ts index 107e330309c..b7823699af2 100644 --- a/packages/plugins/typescript/operations/src/index.ts +++ b/packages/plugins/typescript/operations/src/index.ts @@ -8,9 +8,10 @@ import { TypeScriptDocumentsVisitor } from './visitor.js'; export { TypeScriptDocumentsPluginConfig } from './config.js'; export const plugin: PluginFunction = async ( - inputSchema: GraphQLSchema, - rawDocuments: Types.DocumentFile[], - config: TypeScriptDocumentsPluginConfig + inputSchema, + rawDocuments, + config, + { outputFile } ) => { const schema = config.nullability?.errorHandlingClient ? await semanticToStrict(inputSchema) : inputSchema; @@ -21,7 +22,7 @@ export const plugin: PluginFunction v.document)); - const visitor = new TypeScriptDocumentsVisitor(schema, config, allAst); + const visitor = new TypeScriptDocumentsVisitor(schema, config, allAst, outputFile); const operationsResult = oldVisit(allAst, { leave: visitor }); @@ -63,6 +64,7 @@ export const plugin: PluginFunction { protected _usedNamedInputTypes: UsedNamedInputTypes = {}; - constructor(schema: GraphQLSchema, config: TypeScriptDocumentsPluginConfig, documentNode: DocumentNode) { + private _outputPath: string; + + constructor( + schema: GraphQLSchema, + config: TypeScriptDocumentsPluginConfig, + documentNode: DocumentNode, + outputPath: string + ) { super( config, { @@ -80,6 +89,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< schema ); + this._outputPath = outputPath; autoBind(this); const preResolveTypes = getConfigValue(config.preResolveTypes, true); @@ -175,7 +185,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< EnumTypeDefinition(node: EnumTypeDefinitionNode): string | null { const enumName = node.name.value; - if (!this._usedNamedInputTypes[enumName]) { + if (!this._usedNamedInputTypes[enumName] || this.config.importSchemaTypesFrom) { return null; } @@ -205,15 +215,42 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< : []; } + public getExternalSchemaTypeImports(): Array { + if (!this.config.importSchemaTypesFrom) { + return []; + } + + const hasTypesToImport = Object.keys(this._usedNamedInputTypes).length > 0; + + if (!hasTypesToImport) { + return []; + } + + return [ + generateImportStatement({ + baseDir: process.cwd(), + baseOutputDir: '', + outputPath: this._outputPath, + importSource: { + path: this.config.importSchemaTypesFrom, + namespace: this.config.namespacedImportName, + identifiers: [], + }, + typesImport: true, + // FIXME: rebase with master for the new extension + emitLegacyCommonJSImports: true, + }), + ]; + } + protected getPunctuation(_declarationKind: DeclarationKind): string { return ';'; } protected applyVariablesWrapper(variablesBlock: string, operationType: string): string { - const prefix = this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : ''; const extraType = this.config.allowUndefinedQueryVariables && operationType === 'Query' ? ' | undefined' : ''; - return `${prefix}Exact<${variablesBlock === '{}' ? `{ [key: string]: never; }` : variablesBlock}>${extraType}`; + return `Exact<${variablesBlock === '{}' ? `{ [key: string]: never; }` : variablesBlock}>${extraType}`; } private collectUsedInputTypes({ @@ -236,7 +273,8 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< foundInputType && (foundInputType instanceof GraphQLInputObjectType || foundInputType instanceof GraphQLScalarType || - foundInputType instanceof GraphQLEnumType) + foundInputType instanceof GraphQLEnumType) && + !isNativeNamedType(foundInputType) ) { usedInputTypes[namedTypeNode.name.value] = foundInputType; } diff --git a/packages/plugins/typescript/operations/tests/__snapshots__/ts-documents.spec.ts.snap b/packages/plugins/typescript/operations/tests/__snapshots__/ts-documents.spec.ts.snap index f364438cc65..b9d144384f1 100644 --- a/packages/plugins/typescript/operations/tests/__snapshots__/ts-documents.spec.ts.snap +++ b/packages/plugins/typescript/operations/tests/__snapshots__/ts-documents.spec.ts.snap @@ -57,7 +57,7 @@ export type ElementMetadataFragment = `; exports[`TypeScript Operations Plugin > Issues > #2916 - Missing import prefix with preResolveTypes: true and near-operation-file preset 1`] = ` -"export type UserQueryVariables = Types.Exact<{ [key: string]: never; }>; +"export type UserQueryVariables = Exact<{ [key: string]: never; }>; export type UserQuery = { user: { id: string, username: string, email: string, dep: Types.Department } }; diff --git a/packages/plugins/typescript/operations/tests/ts-documents.nullability.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.nullability.spec.ts index 8f7e8a7c6ee..fb93cae9890 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.nullability.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.nullability.spec.ts @@ -62,11 +62,16 @@ const document = parse(/* GraphQL */ ` describe('TypeScript Operations Plugin - nullability', () => { it('converts semanticNonNull to nonNull when nullability.errorHandlingClient=true', async () => { - const result = await plugin(schema, [{ document }], { - nullability: { - errorHandlingClient: true, + const result = await plugin( + schema, + [{ document }], + { + nullability: { + errorHandlingClient: true, + }, }, - }); + { outputFile: '' } + ); const formattedContent = prettier.format(result.content, { parser: 'typescript' }); expect(formattedContent).toMatchInlineSnapshot(` @@ -103,11 +108,16 @@ describe('TypeScript Operations Plugin - nullability', () => { }); it('does not convert nullability to nonNull when nullability.errorHandlingClient=false', async () => { - const result = await plugin(schema, [{ document }], { - nullability: { - errorHandlingClient: false, + const result = await plugin( + schema, + [{ document }], + { + nullability: { + errorHandlingClient: false, + }, }, - }); + { outputFile: '' } + ); const formattedContent = prettier.format(result.content, { parser: 'typescript' }); expect(formattedContent).toMatchInlineSnapshot(` diff --git a/packages/plugins/typescript/operations/tests/ts-documents.standalone.enum.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.standalone.enum.spec.ts index b00857d7364..d058165b47e 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.enum.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.enum.spec.ts @@ -32,7 +32,7 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], {})]); + const result = mergeOutputs([await plugin(schema, [{ document }], {}, { outputFile: '' })]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -74,7 +74,9 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-numeric' })]); + const result = mergeOutputs([ + await plugin(schema, [{ document }], { enumType: 'native-numeric' }, { outputFile: '' }), + ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -126,7 +128,7 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'const' })]); + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'const' }, { outputFile: '' })]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -182,7 +184,9 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-const' })]); + const result = mergeOutputs([ + await plugin(schema, [{ document }], { enumType: 'native-const' }, { outputFile: '' }), + ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -233,7 +237,7 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' }, { outputFile: '' })]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -285,17 +289,22 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - enumType: 'string-literal', - enumValues: { - UserRole: { - A_B_C: 0, - X_Y_Z: 'Foo', - _TEST: 'Bar', - My_Value: 1, + await plugin( + schema, + [{ document }], + { + enumType: 'string-literal', + enumValues: { + UserRole: { + A_B_C: 0, + X_Y_Z: 'Foo', + _TEST: 'Bar', + My_Value: 1, + }, }, }, - }), + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` @@ -349,17 +358,22 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - enumType: 'const', - enumValues: { - UserRole: { - A_B_C: 0, - X_Y_Z: 'Foo', - _TEST: 'Bar', - My_Value: 1, + await plugin( + schema, + [{ document }], + { + enumType: 'const', + enumValues: { + UserRole: { + A_B_C: 0, + X_Y_Z: 'Foo', + _TEST: 'Bar', + My_Value: 1, + }, }, }, - }), + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` @@ -413,15 +427,20 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - enumType: 'native', - enumValues: { - UserRole: { - ADMIN: 0, - CUSTOMER: 'test', + await plugin( + schema, + [{ document }], + { + enumType: 'native', + enumValues: { + UserRole: { + ADMIN: 0, + CUSTOMER: 'test', + }, }, }, - }), + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` @@ -473,11 +492,16 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - enumValues: { - UserRole: './my-file#MyEnum', + await plugin( + schema, + [{ document }], + { + enumValues: { + UserRole: './my-file#MyEnum', + }, }, - }), + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` @@ -527,11 +551,16 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - enumValues: { - UserRole: './my-file#NS.ETest', + await plugin( + schema, + [{ document }], + { + enumValues: { + UserRole: './my-file#NS.ETest', + }, }, - }), + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` @@ -582,11 +611,16 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - enumValues: { - UserRole: './my-file#NS.UserRole', + await plugin( + schema, + [{ document }], + { + enumValues: { + UserRole: './my-file#NS.UserRole', + }, }, - }), + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` @@ -642,10 +676,15 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - enumType: 'native', - enumValues: './my-file', - }), + await plugin( + schema, + [{ document }], + { + enumType: 'native', + enumValues: './my-file', + }, + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` @@ -704,10 +743,15 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - enumType: 'native', - enumValues: { UserRole: './my-file#UserRole', UserStatus: './my-file#UserStatus2X' }, - }), + await plugin( + schema, + [{ document }], + { + enumType: 'native', + enumValues: { UserRole: './my-file#UserRole', UserStatus: './my-file#UserStatus2X' }, + }, + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` @@ -762,7 +806,7 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' }, { outputFile: '' })]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -815,7 +859,7 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' }, { outputFile: '' })]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -866,7 +910,9 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { typesPrefix: 'I', enumPrefix: true })]); + const result = mergeOutputs([ + await plugin(schema, [{ document }], { typesPrefix: 'I', enumPrefix: true }, { outputFile: '' }), + ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; export type IUserRole = @@ -914,7 +960,9 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { typesPrefix: 'I', enumPrefix: false })]); + const result = mergeOutputs([ + await plugin(schema, [{ document }], { typesPrefix: 'I', enumPrefix: false }, { outputFile: '' }), + ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; export type UserRole = @@ -962,7 +1010,9 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { typesSuffix: 'Z', enumSuffix: true })]); + const result = mergeOutputs([ + await plugin(schema, [{ document }], { typesSuffix: 'Z', enumSuffix: true }, { outputFile: '' }), + ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; export type UserRoleZ = @@ -1010,7 +1060,9 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { typesSuffix: 'Z', enumSuffix: false })]); + const result = mergeOutputs([ + await plugin(schema, [{ document }], { typesSuffix: 'Z', enumSuffix: false }, { outputFile: '' }), + ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; export type UserRole = @@ -1059,12 +1111,17 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - namingConvention: { - typeNames: 'change-case-all#lowerCase', - enumValues: 'keep', + await plugin( + schema, + [{ document }], + { + namingConvention: { + typeNames: 'change-case-all#lowerCase', + enumValues: 'keep', + }, }, - }), + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -1114,13 +1171,18 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - enumType: 'native', - namingConvention: { - typeNames: 'keep', - enumValues: 'change-case-all#lowerCase', + await plugin( + schema, + [{ document }], + { + enumType: 'native', + namingConvention: { + typeNames: 'keep', + enumValues: 'change-case-all#lowerCase', + }, }, - }), + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -1171,9 +1233,14 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - noExport: true, - }), + await plugin( + schema, + [{ document }], + { + noExport: true, + }, + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -1223,13 +1290,18 @@ describe('TypeScript Operations Plugin - Enum', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { - typesPrefix: 'I', - namingConvention: { enumValues: 'change-case-all#constantCase' }, - enumValues: { - UserRole: './files#default as UserRole', + await plugin( + schema, + [{ document }], + { + typesPrefix: 'I', + namingConvention: { enumValues: 'change-case-all#constantCase' }, + enumValues: { + UserRole: './files#default as UserRole', + }, }, - }), + { outputFile: '' } + ), ]); expect(result).toMatchInlineSnapshot(` @@ -1277,7 +1349,7 @@ describe('TypeScript Operations Plugin - Enum', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' }, { outputFile: '' })]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -1327,7 +1399,7 @@ describe('TypeScript Operations Plugin - Enum `%future added value`', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { futureProofEnums: true })]); + const result = mergeOutputs([await plugin(schema, [{ document }], { futureProofEnums: true }, { outputFile: '' })]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; diff --git a/packages/plugins/typescript/operations/tests/ts-documents.standalone.import-types.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.standalone.import-types.spec.ts new file mode 100644 index 00000000000..af820bf713a --- /dev/null +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.import-types.spec.ts @@ -0,0 +1,371 @@ +import { buildSchema, parse } from 'graphql'; +import { validateTs } from '@graphql-codegen/testing'; +import { mergeOutputs } from '@graphql-codegen/plugin-helpers'; +import { plugin } from '../src/index.js'; + +describe('TypeScript Operations Plugin - Import Types', () => { + it('imports user-defined types externally with relative importSchemaTypesFrom correctly', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + user(id: ID!): User + users(input: UsersInput!): UsersResponse! + } + + type ResponseError { + error: ResponseErrorType! + } + + enum ResponseErrorType { + NOT_FOUND + INPUT_VALIDATION_ERROR + FORBIDDEN_ERROR + UNEXPECTED_ERROR + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + "UserRole Description" + enum UserRole { + "UserRole ADMIN" + ADMIN + "UserRole CUSTOMER" + CUSTOMER + } + + "UsersInput Description" + input UsersInput { + "UsersInput from" + from: DateTime + "UsersInput to" + to: DateTime + role: UserRole + } + + type UsersResponseOk { + result: [User!]! + } + union UsersResponse = UsersResponseOk | ResponseError + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query User($id: ID!) { + user(id: $id) { + id + name + role + createdAt + } + } + + query Users($input: UsersInput!) { + users(input: $input) { + ... on UsersResponseOk { + result { + id + } + } + ... on ResponseError { + error + } + } + } + + query UsersWithScalarInput($from: DateTime!, $to: DateTime, $role: UserRole) { + users(input: { from: $from, to: $to, role: $role }) { + ... on UsersResponseOk { + result { + __typename + } + } + ... on ResponseError { + __typename + } + } + } + `); + + const result = mergeOutputs([ + await plugin( + schema, + [{ document }], + { + importSchemaTypesFrom: './base-dir/path-to-other-file.generated.ts', + namespacedImportName: 'TypeImport', + }, + { outputFile: './base-dir/this-file.ts' } + ), + ]); + + expect(result).toMatchInlineSnapshot(` + "import type * as TypeImport from './path-to-other-file.generated'; + + type Exact = { [K in keyof T]: T[K] }; + export type UserQueryVariables = Exact<{ + id: string; + }>; + + + export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: TypeImport.UserRole, createdAt: any } | null }; + + export type UsersQueryVariables = Exact<{ + input: TypeImport.UsersInput; + }>; + + + export type UsersQuery = { __typename?: 'Query', users: + | { __typename?: 'UsersResponseOk', result: Array<{ __typename?: 'User', id: string }> } + | { __typename?: 'ResponseError', error: TypeImport.ResponseErrorType } + }; + + export type UsersWithScalarInputQueryVariables = Exact<{ + from: any; + to?: any | null; + role?: TypeImport.UserRole | null; + }>; + + + export type UsersWithScalarInputQuery = { __typename?: 'Query', users: + | { __typename?: 'UsersResponseOk', result: Array<{ __typename: 'User' }> } + | { __typename: 'ResponseError' } + }; + " + `); + }); + + it('imports user-defined types externally with absolute importSchemaTypesFrom correctly', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + user(id: ID!): User + users(input: UsersInput!): UsersResponse! + } + + type ResponseError { + error: ResponseErrorType! + } + + enum ResponseErrorType { + NOT_FOUND + INPUT_VALIDATION_ERROR + FORBIDDEN_ERROR + UNEXPECTED_ERROR + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + "UserRole Description" + enum UserRole { + "UserRole ADMIN" + ADMIN + "UserRole CUSTOMER" + CUSTOMER + } + + "UsersInput Description" + input UsersInput { + "UsersInput from" + from: DateTime + "UsersInput to" + to: DateTime + role: UserRole + } + + type UsersResponseOk { + result: [User!]! + } + union UsersResponse = UsersResponseOk | ResponseError + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query User($id: ID!) { + user(id: $id) { + id + name + role + createdAt + } + } + + query Users($input: UsersInput!) { + users(input: $input) { + ... on UsersResponseOk { + result { + id + } + } + ... on ResponseError { + error + } + } + } + + query UsersWithScalarInput($from: DateTime!, $to: DateTime, $role: UserRole) { + users(input: { from: $from, to: $to, role: $role }) { + ... on UsersResponseOk { + result { + __typename + } + } + ... on ResponseError { + __typename + } + } + } + `); + + const result = mergeOutputs([ + await plugin( + schema, + [{ document }], + { + importSchemaTypesFrom: '~@my-company/package/types', + namespacedImportName: 'TypeImport', + }, + { outputFile: './base-dir/this-file.ts' } + ), + ]); + + expect(result).toMatchInlineSnapshot(` + "import type * as TypeImport from '@my-company/package/types'; + + type Exact = { [K in keyof T]: T[K] }; + export type UserQueryVariables = Exact<{ + id: string; + }>; + + + export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: TypeImport.UserRole, createdAt: any } | null }; + + export type UsersQueryVariables = Exact<{ + input: TypeImport.UsersInput; + }>; + + + export type UsersQuery = { __typename?: 'Query', users: + | { __typename?: 'UsersResponseOk', result: Array<{ __typename?: 'User', id: string }> } + | { __typename?: 'ResponseError', error: TypeImport.ResponseErrorType } + }; + + export type UsersWithScalarInputQueryVariables = Exact<{ + from: any; + to?: any | null; + role?: TypeImport.UserRole | null; + }>; + + + export type UsersWithScalarInputQuery = { __typename?: 'Query', users: + | { __typename?: 'UsersResponseOk', result: Array<{ __typename: 'User' }> } + | { __typename: 'ResponseError' } + }; + " + `); + }); + + it('does not import external types if only native GraphQL types are used in Variables and Result', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + user(id: ID!): User + users(input: UsersInput!): UsersResponse! + } + + type ResponseError { + error: ResponseErrorType! + } + + enum ResponseErrorType { + NOT_FOUND + INPUT_VALIDATION_ERROR + FORBIDDEN_ERROR + UNEXPECTED_ERROR + } + + type User { + # Native GraphQL types + id: ID! + name: String! + isOld: Boolean! + ageInt: Int! + ageFloat: Float! + + # User-defined types + role: UserRole! + createdAt: DateTime! + } + + "UserRole Description" + enum UserRole { + "UserRole ADMIN" + ADMIN + "UserRole CUSTOMER" + CUSTOMER + } + + "UsersInput Description" + input UsersInput { + "UsersInput from" + from: DateTime + "UsersInput to" + to: DateTime + role: UserRole + } + + type UsersResponseOk { + result: [User!]! + } + union UsersResponse = UsersResponseOk | ResponseError + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query User($id: ID, $name: String, $bool: Boolean, $int: Int, $float: Float) { + user(id: $id) { + id + name + isOld + ageInt + ageFloat + } + } + `); + + const result = mergeOutputs([ + await plugin( + schema, + [{ document }], + { + importSchemaTypesFrom: './path-to-other-file', + namespacedImportName: 'TypeImport', + }, + { outputFile: '' } + ), + ]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type UserQueryVariables = Exact<{ + id?: string | null; + name?: string | null; + bool?: boolean | null; + int?: number | null; + float?: number | null; + }>; + + + export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, isOld: boolean, ageInt: number, ageFloat: number } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); +}); diff --git a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts index 2f1cb9a5485..00ea9a5ca42 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -90,7 +90,7 @@ describe('TypeScript Operations Plugin - Standalone', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], {})]); + const result = mergeOutputs([await plugin(schema, [{ document }], {}, { outputFile: '' })]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; @@ -157,7 +157,7 @@ describe('TypeScript Operations Plugin - Standalone', () => { `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { scalars: { ID: 'string | number | boolean' } }), + await plugin(schema, [{ document }], { scalars: { ID: 'string | number | boolean' } }, { outputFile: '' }), ]); expect(result).toMatchInlineSnapshot(` @@ -283,7 +283,9 @@ describe('TypeScript Operations Plugin - Standalone', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { generatesOperationTypes: false })]); + const result = mergeOutputs([ + await plugin(schema, [{ document }], { generatesOperationTypes: false }, { outputFile: '' }), + ]); expect(result).toMatchInlineSnapshot(` " @@ -299,4 +301,81 @@ describe('TypeScript Operations Plugin - Standalone', () => { validateTs(result, undefined, undefined, undefined, undefined, true); }); + + it('does not generate unused schema enum and input types', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + user(id: ID!): User + users(input: UsersInput!): UsersResponse! + } + + type Mutation { + makeUserAdmin(id: ID!): User! + } + + type Subscription { + userChanges(id: ID!): User! + } + + type ResponseError { + error: ResponseErrorType! + } + + enum ResponseErrorType { + NOT_FOUND + INPUT_VALIDATION_ERROR + FORBIDDEN_ERROR + UNEXPECTED_ERROR + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + "UserRole Description" + enum UserRole { + "UserRole ADMIN" + ADMIN + "UserRole CUSTOMER" + CUSTOMER + } + + "UsersInput Description" + input UsersInput { + "UsersInput from" + from: DateTime + "UsersInput to" + to: DateTime + role: UserRole + } + + type UsersResponseOk { + result: [User!]! + } + union UsersResponse = UsersResponseOk | ResponseError + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query User { + user(id: "100") { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { generatesOperationTypes: false }, { outputFile: '' }), + ]); + + expect(result).toMatchInlineSnapshot(` + " + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); });