From 7f99817b84bbe7473ce85e21dae6ff92fd3a3198 Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Mon, 1 Dec 2025 20:41:44 -0500 Subject: [PATCH 01/20] input types, input/output enums are generated to the target files --- dev-test/star-wars/types.avoidOptionals.ts | 10 ++ dev-test/star-wars/types.excludeQueryAlpha.ts | 10 ++ dev-test/star-wars/types.excludeQueryBeta.ts | 10 ++ .../star-wars/types.globallyAvailable.d.ts | 10 ++ dev-test/star-wars/types.immutableTypes.ts | 10 ++ ...ypes.preResolveTypes.onlyOperationTypes.ts | 10 ++ dev-test/star-wars/types.preResolveTypes.ts | 10 ++ dev-test/star-wars/types.skipSchema.ts | 10 ++ dev-test/star-wars/types.ts | 10 ++ .../typescript/operations/src/index.ts | 30 ++-- .../typescript/operations/src/visitor.ts | 142 ++++++++++++++++++ .../tests/extract-all-types.spec.ts | 40 ++++- .../tests/ts-documents.standalone.spec.ts | 14 +- 13 files changed, 291 insertions(+), 25 deletions(-) diff --git a/dev-test/star-wars/types.avoidOptionals.ts b/dev-test/star-wars/types.avoidOptionals.ts index 96a96ea0b4d..be304fc5c1f 100644 --- a/dev-test/star-wars/types.avoidOptionals.ts +++ b/dev-test/star-wars/types.avoidOptionals.ts @@ -249,6 +249,16 @@ export type Episode = /** Star Wars Episode IV: A New Hope, released in 1977. */ | 'NEWHOPE'; +/** The input object sent when someone is creating a new review */ +export type ReviewInput = { + /** Comment about the movie, optional */ + commentary: string; + /** Favorite color, optional */ + favoriteColor: ColorInput; + /** 0-5 stars */ + stars: number; +}; + 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..540f85e5c25 100644 --- a/dev-test/star-wars/types.excludeQueryAlpha.ts +++ b/dev-test/star-wars/types.excludeQueryAlpha.ts @@ -249,6 +249,16 @@ export type Episode = /** Star Wars Episode IV: A New Hope, released in 1977. */ | 'NEWHOPE'; +/** The input object sent when someone is creating a new review */ +export type ReviewInput = { + /** Comment about the movie, optional */ + commentary: string; + /** Favorite color, optional */ + favoriteColor: ColorInput; + /** 0-5 stars */ + stars: number; +}; + 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..9233ecfac9f 100644 --- a/dev-test/star-wars/types.excludeQueryBeta.ts +++ b/dev-test/star-wars/types.excludeQueryBeta.ts @@ -249,6 +249,16 @@ export type Episode = /** Star Wars Episode IV: A New Hope, released in 1977. */ | 'NEWHOPE'; +/** The input object sent when someone is creating a new review */ +export type ReviewInput = { + /** Comment about the movie, optional */ + commentary: string; + /** Favorite color, optional */ + favoriteColor: ColorInput; + /** 0-5 stars */ + stars: number; +}; + 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..3e569d17f5b 100644 --- a/dev-test/star-wars/types.globallyAvailable.d.ts +++ b/dev-test/star-wars/types.globallyAvailable.d.ts @@ -247,6 +247,16 @@ type Episode = /** Star Wars Episode IV: A New Hope, released in 1977. */ | 'NEWHOPE'; +/** The input object sent when someone is creating a new review */ +type ReviewInput = { + /** Comment about the movie, optional */ + commentary: string; + /** Favorite color, optional */ + favoriteColor: ColorInput; + /** 0-5 stars */ + stars: number; +}; + 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..bea03b64839 100644 --- a/dev-test/star-wars/types.immutableTypes.ts +++ b/dev-test/star-wars/types.immutableTypes.ts @@ -249,6 +249,16 @@ export type Episode = /** Star Wars Episode IV: A New Hope, released in 1977. */ | 'NEWHOPE'; +/** The input object sent when someone is creating a new review */ +export type ReviewInput = { + /** Comment about the movie, optional */ + commentary: string; + /** Favorite color, optional */ + favoriteColor: ColorInput; + /** 0-5 stars */ + stars: number; +}; + 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..c503da95b02 100644 --- a/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts +++ b/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts @@ -58,6 +58,16 @@ export type Episode = /** Star Wars Episode IV: A New Hope, released in 1977. */ | 'NEWHOPE'; +/** The input object sent when someone is creating a new review */ +export type ReviewInput = { + /** Comment about the movie, optional */ + commentary: string; + /** Favorite color, optional */ + favoriteColor: ColorInput; + /** 0-5 stars */ + stars: number; +}; + 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..42cd01885f4 100644 --- a/dev-test/star-wars/types.preResolveTypes.ts +++ b/dev-test/star-wars/types.preResolveTypes.ts @@ -249,6 +249,16 @@ export type Episode = /** Star Wars Episode IV: A New Hope, released in 1977. */ | 'NEWHOPE'; +/** The input object sent when someone is creating a new review */ +export type ReviewInput = { + /** Comment about the movie, optional */ + commentary: string; + /** Favorite color, optional */ + favoriteColor: ColorInput; + /** 0-5 stars */ + stars: number; +}; + 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..42cd01885f4 100644 --- a/dev-test/star-wars/types.skipSchema.ts +++ b/dev-test/star-wars/types.skipSchema.ts @@ -249,6 +249,16 @@ export type Episode = /** Star Wars Episode IV: A New Hope, released in 1977. */ | 'NEWHOPE'; +/** The input object sent when someone is creating a new review */ +export type ReviewInput = { + /** Comment about the movie, optional */ + commentary: string; + /** Favorite color, optional */ + favoriteColor: ColorInput; + /** 0-5 stars */ + stars: number; +}; + 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..42cd01885f4 100644 --- a/dev-test/star-wars/types.ts +++ b/dev-test/star-wars/types.ts @@ -249,6 +249,16 @@ export type Episode = /** Star Wars Episode IV: A New Hope, released in 1977. */ | 'NEWHOPE'; +/** The input object sent when someone is creating a new review */ +export type ReviewInput = { + /** Comment about the movie, optional */ + commentary: string; + /** Favorite color, optional */ + favoriteColor: ColorInput; + /** 0-5 stars */ + stars: number; +}; + export type CreateReviewForEpisodeMutationVariables = Exact<{ episode: Episode; review: ReviewInput; diff --git a/packages/plugins/typescript/operations/src/index.ts b/packages/plugins/typescript/operations/src/index.ts index 8dd2c1b3439..82a6ed6d94d 100644 --- a/packages/plugins/typescript/operations/src/index.ts +++ b/packages/plugins/typescript/operations/src/index.ts @@ -25,25 +25,14 @@ export const plugin: PluginFunction typeof def === 'string').join('\n'); + const schemaTypesDefinitions = schemaTypes.definitions.filter(def => typeof def === 'string'); + + let content = [...schemaTypesDefinitions, ...operationsDefinitions].join('\n'); - const content: string[] = []; - if (schemaTypesContent) { - content.push(schemaTypesContent); + if (config.globalNamespace) { + content = ` + declare global { + ${content} + }`; } - content.push(operationsContent); return { prepend: [ @@ -66,7 +58,7 @@ export const plugin: PluginFunction = { [K in keyof T]: T[K] };', ], - content: content.join('\n'), + content, }; }; diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 3e4ae45692b..f2ad7853da9 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -2,9 +2,12 @@ import { BaseDocumentsVisitor, type ConvertSchemaEnumToDeclarationBlockString, convertSchemaEnumToDeclarationBlockString, + DeclarationBlock, DeclarationKind, generateFragmentImportStatement, getConfigValue, + indent, + isOneOfInputObjectType, LoadedFragment, normalizeAvoidOptionals, NormalizedAvoidOptionalsConfig, @@ -14,6 +17,7 @@ import { PreResolveTypesProcessor, SelectionSetProcessorConfig, SelectionSetToObject, + transformComment, wrapTypeWithModifiers, } from '@graphql-codegen/visitor-plugin-common'; import autoBind from 'auto-bind'; @@ -21,6 +25,7 @@ import { type DocumentNode, EnumTypeDefinitionNode, type FragmentDefinitionNode, + getNamedType, GraphQLEnumType, GraphQLInputObjectType, type GraphQLNamedInputType, @@ -28,10 +33,17 @@ import { type GraphQLOutputType, GraphQLScalarType, type GraphQLSchema, + InputObjectTypeDefinitionNode, + InputValueDefinitionNode, isEnumType, isNonNullType, Kind, + ListTypeNode, + NamedTypeNode, + NonNullTypeNode, + TypeInfo, visit, + visitWithTypeInfo, } from 'graphql'; import { TypeScriptDocumentsPluginConfig } from './config.js'; import { TypeScriptOperationVariablesToObject } from './ts-operation-variables-to-object.js'; @@ -168,6 +180,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< ); this._declarationBlockConfig = { ignoreExport: this.config.noExport, + enumNameValueSeparator: ' =', }; } @@ -196,6 +209,110 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< }); } + InputObjectTypeDefinition(node: InputObjectTypeDefinitionNode): string | null { + const inputTypeName = node.name.value; + if (!this._usedNamedInputTypes[inputTypeName]) { + return null; + } + + if (isOneOfInputObjectType(this._schema.getType(inputTypeName))) { + return this.getInputObjectOneOfDeclarationBlock(node).string; + } + + return this.getInputObjectDeclarationBlock(node).string; + } + + private getInputObjectDeclarationBlock(node: InputObjectTypeDefinitionNode): DeclarationBlock { + return new DeclarationBlock(this._declarationBlockConfig) + .export() + .asKind('type') + .withName(this.convertName(node)) + .withComment(node.description?.value) + .withBlock((node.fields || []).join('\n')); + } + + private getInputObjectOneOfDeclarationBlock(node: InputObjectTypeDefinitionNode): DeclarationBlock { + const declarationKind = (node.fields?.length || 0) === 1 ? 'type' : 'type'; + return new DeclarationBlock(this._declarationBlockConfig) + .export() + .asKind(declarationKind) + .withName(this.convertName(node)) + .withComment(node.description?.value) + .withContent(`\n` + (node.fields || []).join('\n |')); + } + + private isValidVisitor(ancestors: any): boolean { + const currentVisitContext = this.getVisitorKindContextFromAncestors(ancestors); + const isVisitingInputType = currentVisitContext.includes(Kind.INPUT_OBJECT_TYPE_DEFINITION); + const isVisitingEnumType = currentVisitContext.includes(Kind.ENUM_TYPE_DEFINITION); + const isVisitingOperation = currentVisitContext.includes(Kind.OPERATION_DEFINITION); + + if (isVisitingOperation) { + return false; + } + + if (!isVisitingInputType && !isVisitingEnumType) { + return false; + } + + return true; + } + + InputValueDefinition(node: InputValueDefinitionNode): string { + const comment = transformComment(node.description?.value || '', 1); + const type: string = node.type as any as string; + return comment + indent(`${node.name.value}: ${type};`); + } + + NamedType(node: NamedTypeNode, _key: any, _parent: any, _path: any, ancestors: any): string | undefined { + if (!this.isValidVisitor(ancestors)) { + return undefined; + } + + const schemaType = this._schema.getType(node.name.value); + + // For scalars, use the configured scalar type (use input property for input context) + if (schemaType instanceof GraphQLScalarType) { + const scalarConfig = this.scalars[node.name.value]; + if (scalarConfig && 'input' in scalarConfig) { + // scalarConfig.input is already the type string (extracted from ParsedMapper in BaseVisitor) + const inputType = scalarConfig.input; + // If the type is 'any', use the scalar name itself instead (for custom scalars) + if (inputType === 'any') { + return node.name.value; + } + return inputType; + } + // Fallback to scalar name + return node.name.value; + } + + // For enums and input types, use the converted name + if (schemaType instanceof GraphQLEnumType || schemaType instanceof GraphQLInputObjectType) { + return this.convertName(node.name.value); + } + + return node.name.value; + } + + ListType(node: ListTypeNode, _key: any, _parent: any, _path: any, ancestors: any): string | undefined { + if (!this.isValidVisitor(ancestors)) { + return undefined; + } + + const asString = node.type as any as string; + const listModifier = this.config.immutableTypes ? 'ReadonlyArray' : 'Array'; + return `${listModifier}<${asString}>`; + } + + NonNullType(node: NonNullTypeNode, _key: any, _parent: any, _path: any, ancestors: any): string | undefined { + if (!this.isValidVisitor(ancestors)) { + return undefined; + } + + return node.type as any as string; + } + public getImports(): Array { return !this.config.globalNamespace && (this.config.inlineFragmentTypes === 'combine' || this.config.inlineFragmentTypes === 'mask') @@ -225,6 +342,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< const usedInputTypes: UsedNamedInputTypes = {}; + // First collect types from variable definitions visit(documentNode, { VariableDefinition: variableDefinitionNode => { visit(variableDefinitionNode, { @@ -243,6 +361,30 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< }, }); + // Only collect enums from output types when not using namespacedImportName + // When namespacedImportName is set, enums should come from the types package + if (!this.config.namespacedImportName) { + const typeInfo = new TypeInfo(schema); + + visit( + documentNode, + visitWithTypeInfo(typeInfo, { + Field: () => { + // Get the type of the current field + const fieldType = typeInfo.getType(); + if (fieldType) { + const namedType = getNamedType(fieldType); + + // If it's an enum, add it + if (namedType instanceof GraphQLEnumType) { + usedInputTypes[namedType.name] = namedType; + } + } + }, + }) + ); + } + return usedInputTypes; } } 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 c64468cf85c..d24fe00a07c 100644 --- a/packages/plugins/typescript/operations/tests/extract-all-types.spec.ts +++ b/packages/plugins/typescript/operations/tests/extract-all-types.spec.ts @@ -401,7 +401,13 @@ describe('extractAllFieldsToTypes: true', () => { { outputFile: '' } ); expect(content).toMatchInlineSnapshot(` - "export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = { __typename: 'ArchivedArticle', id: string, htmlUrl: string, title: string, url: string }; + "export type CallType = + | 'OUTGOING' + | 'INCOMING' + | 'VOICEMAIL' + | 'UNKNOWN'; + + export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = { __typename: 'ArchivedArticle', id: string, htmlUrl: string, title: string, url: string }; export type ConversationBotSolutionFragment_BotSolution_originatedFrom_EmailInteraction = { __typename: 'EmailInteraction', originalEmailURLPath: string }; @@ -565,7 +571,13 @@ describe('extractAllFieldsToTypes: true', () => { { outputFile: '' } ); expect(content).toMatchInlineSnapshot(` - "export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = ( + "export type CallType = + | 'OUTGOING' + | 'INCOMING' + | 'VOICEMAIL' + | 'UNKNOWN'; + + export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = ( { id: string, htmlUrl: string, title: string, url: string } & { __typename: 'ArchivedArticle' } ); @@ -734,7 +746,13 @@ describe('extractAllFieldsToTypes: true', () => { { outputFile: '' } ); expect(content).toMatchInlineSnapshot(` - "export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = { __typename: 'ArchivedArticle', id: string, htmlUrl: string, title: string, url: string }; + "export type CallType = + | 'OUTGOING' + | 'INCOMING' + | 'VOICEMAIL' + | 'UNKNOWN'; + + export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = { __typename: 'ArchivedArticle', id: string, htmlUrl: string, title: string, url: string }; export type ConversationBotSolutionFragment_BotSolution_originatedFrom_EmailInteraction = ( { __typename: 'EmailInteraction' } @@ -972,7 +990,13 @@ describe('extractAllFieldsToTypes: true', () => { { outputFile: '' } ); expect(content).toMatchInlineSnapshot(` - "export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = { __typename: 'ArchivedArticle', id: string, htmlUrl: string, title: string, url: string }; + "export type CallType = + | 'OUTGOING' + | 'INCOMING' + | 'VOICEMAIL' + | 'UNKNOWN'; + + export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = { __typename: 'ArchivedArticle', id: string, htmlUrl: string, title: string, url: string }; export type ConversationBotSolutionFragment_BotSolution_originatedFrom_EmailInteraction = ( { __typename: 'EmailInteraction' } @@ -1207,7 +1231,13 @@ describe('extractAllFieldsToTypes: true', () => { { outputFile: '' } ); expect(content).toMatchInlineSnapshot(` - "export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = ( + "export type CallType = + | 'OUTGOING' + | 'INCOMING' + | 'VOICEMAIL' + | 'UNKNOWN'; + + export type ConversationBotSolutionFragment_BotSolution_article_ArchivedArticle = ( { __typename: 'ArchivedArticle' } & Pick< ArchivedArticle, 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 06dd1752443..a6f15184d80 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -84,14 +84,26 @@ describe('TypeScript Operations Plugin - Standalone', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], {})]); + const result = mergeOutputs([await plugin(schema, [{ document }], {})]); // enumType: 'string-literal' expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; + export type ResponseErrorType = + | 'NOT_FOUND' + | 'INPUT_VALIDATION_ERROR' + | 'FORBIDDEN_ERROR' + | 'UNEXPECTED_ERROR'; + export type UserRole = | 'ADMIN' | 'CUSTOMER'; + export type UsersInput = { + from: DateTime; + to: DateTime; + role: UserRole; + }; + export type UserQueryVariables = Exact<{ id: string; }>; From aa5e43a981bf65329782dfc0f07c29a27331bd5f Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Mon, 1 Dec 2025 21:10:03 -0500 Subject: [PATCH 02/20] cleanup --- .../typescript/operations/tests/ts-documents.standalone.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a6f15184d80..b50688c3f43 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -84,7 +84,7 @@ describe('TypeScript Operations Plugin - Standalone', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], {})]); // enumType: 'string-literal' + const result = mergeOutputs([await plugin(schema, [{ document }], {})]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; From 8e15362e0ead7b46077951b5f9b243542e719f86 Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Tue, 2 Dec 2025 10:38:06 -0500 Subject: [PATCH 03/20] better code --- .../typescript/operations/src/visitor.ts | 74 ++++++++----------- .../__snapshots__/ts-documents.spec.ts.snap | 6 +- 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index f2ad7853da9..3e40584bd26 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -241,31 +241,22 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< .withContent(`\n` + (node.fields || []).join('\n |')); } - private isValidVisitor(ancestors: any): boolean { - const currentVisitContext = this.getVisitorKindContextFromAncestors(ancestors); - const isVisitingInputType = currentVisitContext.includes(Kind.INPUT_OBJECT_TYPE_DEFINITION); - const isVisitingEnumType = currentVisitContext.includes(Kind.ENUM_TYPE_DEFINITION); - const isVisitingOperation = currentVisitContext.includes(Kind.OPERATION_DEFINITION); - - if (isVisitingOperation) { - return false; - } - - if (!isVisitingInputType && !isVisitingEnumType) { - return false; - } - - return true; - } - InputValueDefinition(node: InputValueDefinitionNode): string { const comment = transformComment(node.description?.value || '', 1); const type: string = node.type as any as string; return comment + indent(`${node.name.value}: ${type};`); } + private isValidVisit(ancestors: any): boolean { + const currentVisitContext = this.getVisitorKindContextFromAncestors(ancestors); + const isVisitingInputType = currentVisitContext.includes(Kind.INPUT_OBJECT_TYPE_DEFINITION); + const isVisitingEnumType = currentVisitContext.includes(Kind.ENUM_TYPE_DEFINITION); + + return isVisitingInputType || isVisitingEnumType; + } + NamedType(node: NamedTypeNode, _key: any, _parent: any, _path: any, ancestors: any): string | undefined { - if (!this.isValidVisitor(ancestors)) { + if (!this.isValidVisit(ancestors)) { return undefined; } @@ -296,17 +287,16 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< } ListType(node: ListTypeNode, _key: any, _parent: any, _path: any, ancestors: any): string | undefined { - if (!this.isValidVisitor(ancestors)) { + if (!this.isValidVisit(ancestors)) { return undefined; } - const asString = node.type as any as string; const listModifier = this.config.immutableTypes ? 'ReadonlyArray' : 'Array'; - return `${listModifier}<${asString}>`; + return `${listModifier}<${node.type}>`; } NonNullType(node: NonNullTypeNode, _key: any, _parent: any, _path: any, ancestors: any): string | undefined { - if (!this.isValidVisitor(ancestors)) { + if (!this.isValidVisit(ancestors)) { return undefined; } @@ -342,7 +332,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< const usedInputTypes: UsedNamedInputTypes = {}; - // First collect types from variable definitions + // Collect input enums and input types visit(documentNode, { VariableDefinition: variableDefinitionNode => { visit(variableDefinitionNode, { @@ -361,29 +351,23 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< }, }); - // Only collect enums from output types when not using namespacedImportName - // When namespacedImportName is set, enums should come from the types package - if (!this.config.namespacedImportName) { - const typeInfo = new TypeInfo(schema); - - visit( - documentNode, - visitWithTypeInfo(typeInfo, { - Field: () => { - // Get the type of the current field - const fieldType = typeInfo.getType(); - if (fieldType) { - const namedType = getNamedType(fieldType); - - // If it's an enum, add it - if (namedType instanceof GraphQLEnumType) { - usedInputTypes[namedType.name] = namedType; - } + // Collect output enums + const typeInfo = new TypeInfo(schema); + visit( + documentNode, + visitWithTypeInfo(typeInfo, { + Field: () => { + const fieldType = typeInfo.getType(); + if (fieldType) { + const namedType = getNamedType(fieldType); + + if (namedType instanceof GraphQLEnumType) { + usedInputTypes[namedType.name] = namedType; } - }, - }) - ); - } + } + }, + }) + ); return usedInputTypes; } 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..3fc18750df5 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,11 @@ 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 Department = + | 'Direction' + | 'Development'; + +export type UserQueryVariables = Types.Exact<{ [key: string]: never; }>; export type UserQuery = { user: { id: string, username: string, email: string, dep: Types.Department } }; From 1ac8a855166bd3cc799c9519c43ce985a4dc943f Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Tue, 2 Dec 2025 14:07:28 -0500 Subject: [PATCH 04/20] more tests --- .../src/ts-operation-variables-to-object.ts | 2 +- .../typescript/operations/src/visitor.ts | 24 +- .../tests/ts-documents.standalone.spec.ts | 252 ++++++++++++++++++ 3 files changed, 261 insertions(+), 17 deletions(-) diff --git a/packages/plugins/typescript/operations/src/ts-operation-variables-to-object.ts b/packages/plugins/typescript/operations/src/ts-operation-variables-to-object.ts index e49bdf673df..b8ea3e147cd 100644 --- a/packages/plugins/typescript/operations/src/ts-operation-variables-to-object.ts +++ b/packages/plugins/typescript/operations/src/ts-operation-variables-to-object.ts @@ -1,6 +1,6 @@ import { TypeScriptOperationVariablesToObject as TSOperationVariablesToObject } from '@graphql-codegen/typescript'; -const SCALARS = { +export const SCALARS = { ID: 'string | number', String: 'string', Int: 'number', diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 3e40584bd26..0a9c71873cd 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -46,7 +46,7 @@ import { visitWithTypeInfo, } from 'graphql'; import { TypeScriptDocumentsPluginConfig } from './config.js'; -import { TypeScriptOperationVariablesToObject } from './ts-operation-variables-to-object.js'; +import { TypeScriptOperationVariablesToObject, SCALARS } from './ts-operation-variables-to-object.js'; import { TypeScriptSelectionSetProcessor } from './ts-selection-set-processor.js'; export interface TypeScriptDocumentsParsedConfig extends ParsedDocumentsConfig { @@ -175,7 +175,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< this.config.enumValues, this.config.arrayInputCoercion, undefined, - 'InputMaybe' + undefined ) ); this._declarationBlockConfig = { @@ -262,23 +262,15 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< const schemaType = this._schema.getType(node.name.value); - // For scalars, use the configured scalar type (use input property for input context) if (schemaType instanceof GraphQLScalarType) { - const scalarConfig = this.scalars[node.name.value]; - if (scalarConfig && 'input' in scalarConfig) { - // scalarConfig.input is already the type string (extracted from ParsedMapper in BaseVisitor) - const inputType = scalarConfig.input; - // If the type is 'any', use the scalar name itself instead (for custom scalars) - if (inputType === 'any') { - return node.name.value; - } + const inputType = this.scalars?.[node.name.value]?.input ?? SCALARS[node.name.value] ?? 'any'; + if (inputType === 'any' && node.name.value) { + return node.name.value; + } return inputType; - } - // Fallback to scalar name - return node.name.value; + } - // For enums and input types, use the converted name if (schemaType instanceof GraphQLEnumType || schemaType instanceof GraphQLInputObjectType) { return this.convertName(node.name.value); } @@ -300,7 +292,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< return undefined; } - return node.type as any as string; + return node.type as any as string | undefined; } public getImports(): Array { 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 b50688c3f43..ad572fe1333 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -139,6 +139,258 @@ describe('TypeScript Operations Plugin - Standalone', () => { // validateTs(content, undefined, undefined, undefined, undefined, true); }); + it('test generating input types lists', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + users(input: UsersInput!): UsersResponse! + } + + type ResponseError { + error: ResponseErrorType! + } + + enum ResponseErrorType { + NOT_FOUND + INPUT_VALIDATION_ERROR + FORBIDDEN_ERROR + UNEXPECTED_ERROR + } + + enum UserRole { + ADMIN + CUSTOMER + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + input UsersInput { + from: DateTime + 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 }], {})]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type ResponseErrorType = + | 'NOT_FOUND' + | 'INPUT_VALIDATION_ERROR' + | 'FORBIDDEN_ERROR' + | 'UNEXPECTED_ERROR'; + + export type UserRole = + | 'ADMIN' + | 'CUSTOMER'; + + export type UsersInput = { + from: DateTime; + to: DateTime; + role: Array; + }; + + export type UserQueryVariables = Exact<{ + id: string; + }>; + + + export type UserQuery = { __typename?: 'Query' }; + + export type UsersQueryVariables = Exact<{ + input: UsersInput; + }>; + + + export type UsersQuery = { __typename?: 'Query', users: + | { __typename?: 'UsersResponseOk', result: Array<{ __typename?: 'User', id: string }> } + | { __typename?: 'ResponseError', error: ResponseErrorType } + }; + + export type UsersWithScalarInputQueryVariables = Exact<{ + from: any; + to?: any | null; + role?: UserRole | null; + }>; + + + export type UsersWithScalarInputQuery = { __typename?: 'Query', users: + | { __typename?: 'UsersResponseOk', result: Array<{ __typename: 'User' }> } + | { __typename: 'ResponseError' } + }; + " + `); + + // FIXME: enable this to ensure type correctness + // validateTs(content, undefined, undefined, undefined, undefined, true); + }); + + it('try different way to generate enums', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + user(id: ID!): User + } + + type User { + id: ID! + name: String! + role: UserRole! + } + + enum UserRole { + ADMIN + CUSTOMER + } + `); + const document = parse(/* GraphQL */ ` + query User($id: ID!) { + user(id: $id) { + id + name + role + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'string-literal' })]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type UserRole = + | 'ADMIN' + | 'CUSTOMER'; + + export type UserQueryVariables = Exact<{ + id: string; + }>; + + + export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; + " + `); + + const result2 = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-numeric' })]); + + expect(result2).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export enum UserRole { + Admin = 0, + Customer = 1 + } + + export type UserQueryVariables = Exact<{ + id: string; + }>; + + + export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; + " + `); + + const result3 = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'const' })]); + + expect(result3).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export const UserRole = { + Admin: 'ADMIN', + Customer: 'CUSTOMER' + } as const; + + export type UserRole = typeof UserRole[keyof typeof UserRole]; + export type UserQueryVariables = Exact<{ + id: string; + }>; + + + export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; + " + `); + + const result4 = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-const' })]); + + expect(result4).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export const enum UserRole { + Admin = 'ADMIN', + Customer = 'CUSTOMER' + }; + + export type UserQueryVariables = Exact<{ + id: string; + }>; + + + export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; + " + `); + + const result5 = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + + expect(result5).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export enum UserRole { + Admin = 'ADMIN', + Customer = 'CUSTOMER' + } + + export type UserQueryVariables = Exact<{ + id: string; + }>; + + + export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; + " + `); + }); + it('test overrdiding config.scalars', async () => { const schema = buildSchema(/* GraphQL */ ` type Query { From 9480528bf9f32ab16eb727dac088952656e7ae61 Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Tue, 2 Dec 2025 14:10:19 -0500 Subject: [PATCH 05/20] cleanup --- packages/plugins/typescript/operations/src/visitor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 0a9c71873cd..9f65ac616d2 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -266,9 +266,9 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< const inputType = this.scalars?.[node.name.value]?.input ?? SCALARS[node.name.value] ?? 'any'; if (inputType === 'any' && node.name.value) { return node.name.value; - } - return inputType; - + } + + return inputType; } if (schemaType instanceof GraphQLEnumType || schemaType instanceof GraphQLInputObjectType) { From 477c9e8728c161e248a30520ec1c84f22b448b11 Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Tue, 2 Dec 2025 14:13:59 -0500 Subject: [PATCH 06/20] better code --- .../plugins/typescript/operations/src/visitor.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 9f65ac616d2..262bcdb83d8 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -222,6 +222,12 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< return this.getInputObjectDeclarationBlock(node).string; } + InputValueDefinition(node: InputValueDefinitionNode): string { + const comment = transformComment(node.description?.value || '', 1); + const type: string = node.type as any as string; + return comment + indent(`${node.name.value}: ${type};`); + } + private getInputObjectDeclarationBlock(node: InputObjectTypeDefinitionNode): DeclarationBlock { return new DeclarationBlock(this._declarationBlockConfig) .export() @@ -241,12 +247,6 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< .withContent(`\n` + (node.fields || []).join('\n |')); } - InputValueDefinition(node: InputValueDefinitionNode): string { - const comment = transformComment(node.description?.value || '', 1); - const type: string = node.type as any as string; - return comment + indent(`${node.name.value}: ${type};`); - } - private isValidVisit(ancestors: any): boolean { const currentVisitContext = this.getVisitorKindContextFromAncestors(ancestors); const isVisitingInputType = currentVisitContext.includes(Kind.INPUT_OBJECT_TYPE_DEFINITION); From 7094891901cd78e90866d63b2123789796da4364 Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Tue, 2 Dec 2025 15:16:50 -0500 Subject: [PATCH 07/20] better tests --- .../tests/ts-documents.standalone.spec.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) 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 ad572fe1333..e20fdd8bb17 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -268,12 +268,9 @@ describe('TypeScript Operations Plugin - Standalone', () => { }; " `); - - // FIXME: enable this to ensure type correctness - // validateTs(content, undefined, undefined, undefined, undefined, true); }); - it('try different way to generate enums', async () => { + it('try different ways to generate enums', async () => { const schema = buildSchema(/* GraphQL */ ` type Query { user(id: ID!): User @@ -300,9 +297,10 @@ describe('TypeScript Operations Plugin - Standalone', () => { } `); - const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'string-literal' })]); + // string-literal + const resultStringLiteral = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'string-literal' })]); - expect(result).toMatchInlineSnapshot(` + expect(resultStringLiteral).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; export type UserRole = | 'ADMIN' @@ -317,9 +315,10 @@ describe('TypeScript Operations Plugin - Standalone', () => { " `); - const result2 = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-numeric' })]); + // native-numeric + const resultNativeNumeric = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-numeric' })]); - expect(result2).toMatchInlineSnapshot(` + expect(resultNativeNumeric).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; export enum UserRole { Admin = 0, @@ -335,9 +334,10 @@ describe('TypeScript Operations Plugin - Standalone', () => { " `); - const result3 = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'const' })]); + // const + const resultConst = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'const' })]); - expect(result3).toMatchInlineSnapshot(` + expect(resultConst).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; export const UserRole = { Admin: 'ADMIN', @@ -354,9 +354,10 @@ describe('TypeScript Operations Plugin - Standalone', () => { " `); - const result4 = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-const' })]); + // native-const + const resultNativeConst = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-const' })]); - expect(result4).toMatchInlineSnapshot(` + expect(resultNativeConst).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; export const enum UserRole { Admin = 'ADMIN', @@ -372,9 +373,10 @@ describe('TypeScript Operations Plugin - Standalone', () => { " `); - const result5 = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + // native + const resultNative = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); - expect(result5).toMatchInlineSnapshot(` + expect(resultNative).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; export enum UserRole { Admin = 'ADMIN', From d4177d80e7c83d179d401283f63ada32de90788d Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Thu, 4 Dec 2025 12:21:23 -0500 Subject: [PATCH 08/20] bugfixing for inner types and outer enums --- dev-test/star-wars/types.avoidOptionals.ts | 7 + dev-test/star-wars/types.excludeQueryAlpha.ts | 7 + dev-test/star-wars/types.excludeQueryBeta.ts | 7 + .../star-wars/types.globallyAvailable.d.ts | 7 + dev-test/star-wars/types.immutableTypes.ts | 7 + ...ypes.preResolveTypes.onlyOperationTypes.ts | 7 + dev-test/star-wars/types.preResolveTypes.ts | 7 + dev-test/star-wars/types.skipSchema.ts | 7 + dev-test/star-wars/types.ts | 7 + .../typescript/operations/src/visitor.ts | 20 + .../tests/ts-documents.standalone.spec.ts | 374 ++++++++++-------- 11 files changed, 285 insertions(+), 172 deletions(-) diff --git a/dev-test/star-wars/types.avoidOptionals.ts b/dev-test/star-wars/types.avoidOptionals.ts index be304fc5c1f..d31bf22709a 100644 --- a/dev-test/star-wars/types.avoidOptionals.ts +++ b/dev-test/star-wars/types.avoidOptionals.ts @@ -240,6 +240,13 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; +/** The input object sent when passing a color */ +export type ColorInput = { + blue: number; + green: number; + red: number; +}; + /** The episodes in the Star Wars trilogy */ export type Episode = /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ diff --git a/dev-test/star-wars/types.excludeQueryAlpha.ts b/dev-test/star-wars/types.excludeQueryAlpha.ts index 540f85e5c25..142bda39b04 100644 --- a/dev-test/star-wars/types.excludeQueryAlpha.ts +++ b/dev-test/star-wars/types.excludeQueryAlpha.ts @@ -240,6 +240,13 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; +/** The input object sent when passing a color */ +export type ColorInput = { + blue: number; + green: number; + red: number; +}; + /** The episodes in the Star Wars trilogy */ export type Episode = /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ diff --git a/dev-test/star-wars/types.excludeQueryBeta.ts b/dev-test/star-wars/types.excludeQueryBeta.ts index 9233ecfac9f..ac64e697f8a 100644 --- a/dev-test/star-wars/types.excludeQueryBeta.ts +++ b/dev-test/star-wars/types.excludeQueryBeta.ts @@ -240,6 +240,13 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; +/** The input object sent when passing a color */ +export type ColorInput = { + blue: number; + green: number; + red: number; +}; + /** The episodes in the Star Wars trilogy */ export type Episode = /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ diff --git a/dev-test/star-wars/types.globallyAvailable.d.ts b/dev-test/star-wars/types.globallyAvailable.d.ts index 3e569d17f5b..8045400d9c8 100644 --- a/dev-test/star-wars/types.globallyAvailable.d.ts +++ b/dev-test/star-wars/types.globallyAvailable.d.ts @@ -238,6 +238,13 @@ type StarshipLengthArgs = { unit?: InputMaybe; }; +/** The input object sent when passing a color */ +type ColorInput = { + blue: number; + green: number; + red: number; +}; + /** The episodes in the Star Wars trilogy */ type Episode = /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ diff --git a/dev-test/star-wars/types.immutableTypes.ts b/dev-test/star-wars/types.immutableTypes.ts index bea03b64839..7d97df5dee9 100644 --- a/dev-test/star-wars/types.immutableTypes.ts +++ b/dev-test/star-wars/types.immutableTypes.ts @@ -240,6 +240,13 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; +/** The input object sent when passing a color */ +export type ColorInput = { + blue: number; + green: number; + red: number; +}; + /** The episodes in the Star Wars trilogy */ export type Episode = /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ diff --git a/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts b/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts index c503da95b02..c96287fddd9 100644 --- a/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts +++ b/dev-test/star-wars/types.preResolveTypes.onlyOperationTypes.ts @@ -49,6 +49,13 @@ export type ReviewInput = { stars: Scalars['Int']['input']; }; +/** The input object sent when passing a color */ +export type ColorInput = { + blue: number; + green: number; + red: number; +}; + /** The episodes in the Star Wars trilogy */ export type Episode = /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ diff --git a/dev-test/star-wars/types.preResolveTypes.ts b/dev-test/star-wars/types.preResolveTypes.ts index 42cd01885f4..11503496d1c 100644 --- a/dev-test/star-wars/types.preResolveTypes.ts +++ b/dev-test/star-wars/types.preResolveTypes.ts @@ -240,6 +240,13 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; +/** The input object sent when passing a color */ +export type ColorInput = { + blue: number; + green: number; + red: number; +}; + /** The episodes in the Star Wars trilogy */ export type Episode = /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ diff --git a/dev-test/star-wars/types.skipSchema.ts b/dev-test/star-wars/types.skipSchema.ts index 42cd01885f4..11503496d1c 100644 --- a/dev-test/star-wars/types.skipSchema.ts +++ b/dev-test/star-wars/types.skipSchema.ts @@ -240,6 +240,13 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; +/** The input object sent when passing a color */ +export type ColorInput = { + blue: number; + green: number; + red: number; +}; + /** The episodes in the Star Wars trilogy */ export type Episode = /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ diff --git a/dev-test/star-wars/types.ts b/dev-test/star-wars/types.ts index 42cd01885f4..11503496d1c 100644 --- a/dev-test/star-wars/types.ts +++ b/dev-test/star-wars/types.ts @@ -240,6 +240,13 @@ export type StarshipLengthArgs = { unit?: InputMaybe; }; +/** The input object sent when passing a color */ +export type ColorInput = { + blue: number; + green: number; + red: number; +}; + /** The episodes in the Star Wars trilogy */ export type Episode = /** Star Wars Episode V: The Empire Strikes Back, released in 1980. */ diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 262bcdb83d8..ecdba9be33b 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -313,6 +313,23 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< return `${prefix}Exact<${variablesBlock === '{}' ? `{ [key: string]: never; }` : variablesBlock}>${extraType}`; } + private collectInnerTypesRecursively(type: GraphQLInputObjectType, usedInputTypes: UsedNamedInputTypes): void { + const fields = type.getFields(); + for (const field of Object.values(fields)) { + const fieldType = getNamedType(field.type); + if (( + fieldType instanceof GraphQLEnumType || + fieldType instanceof GraphQLInputObjectType || + fieldType instanceof GraphQLScalarType + ) && !usedInputTypes[fieldType.name]) { + usedInputTypes[fieldType.name] = fieldType; + if (fieldType instanceof GraphQLInputObjectType) { + this.collectInnerTypesRecursively(fieldType, usedInputTypes); + } + } + } + } + private collectUsedInputTypes({ schema, documentNode, @@ -337,6 +354,9 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< foundInputType instanceof GraphQLEnumType) ) { usedInputTypes[namedTypeNode.name.value] = foundInputType; + if (foundInputType instanceof GraphQLInputObjectType) { + this.collectInnerTypesRecursively(foundInputType, usedInputTypes); + } } }, }); 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 e20fdd8bb17..c82bebfdb5c 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -139,81 +139,45 @@ describe('TypeScript Operations Plugin - Standalone', () => { // validateTs(content, undefined, undefined, undefined, undefined, true); }); - it('test generating input types lists', async () => { + it('test generating input types enums in lists and inner field', async () => { const schema = buildSchema(/* GraphQL */ ` type Query { - users(input: UsersInput!): UsersResponse! + users(input: UsersInput!): [User!]! } - type ResponseError { - error: ResponseErrorType! + type User { + id: ID! } - enum ResponseErrorType { - NOT_FOUND - INPUT_VALIDATION_ERROR - FORBIDDEN_ERROR - UNEXPECTED_ERROR + enum EnumRootLevel { + ENUM_A + ENUM_B } - enum UserRole { - ADMIN - CUSTOMER + enum EnumRootLevelArray { + ENUM_C + ENUM_D } - type User { - id: ID! - name: String! - role: UserRole! - createdAt: DateTime! + enum EnumInnerArray { + ENUM_E + ENUM_F } - input UsersInput { - from: DateTime - to: DateTime - role: [UserRole!]! + input EnumsInner { + enumsDeep: [EnumInnerArray!]! } - type UsersResponseOk { - result: [User!]! + input UsersInput { + enum: EnumRootLevel! + enums: [EnumRootLevelArray!]! + innerEnums: EnumsInner! } - 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 - } + id } } `); @@ -222,209 +186,275 @@ describe('TypeScript Operations Plugin - Standalone', () => { expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; - export type ResponseErrorType = - | 'NOT_FOUND' - | 'INPUT_VALIDATION_ERROR' - | 'FORBIDDEN_ERROR' - | 'UNEXPECTED_ERROR'; - - export type UserRole = - | 'ADMIN' - | 'CUSTOMER'; + export type EnumRootLevel = + | 'ENUM_A' + | 'ENUM_B'; - export type UsersInput = { - from: DateTime; - to: DateTime; - role: Array; - }; + export type EnumRootLevelArray = + | 'ENUM_C' + | 'ENUM_D'; - export type UserQueryVariables = Exact<{ - id: string; - }>; + export type EnumInnerArray = + | 'ENUM_E' + | 'ENUM_F'; + export type EnumsInner = { + enumsDeep: Array; + }; - export type UserQuery = { __typename?: 'Query' }; + export type UsersInput = { + enum: EnumRootLevel; + enums: Array; + innerEnums: EnumsInner; + }; export type UsersQueryVariables = Exact<{ input: UsersInput; }>; - export type UsersQuery = { __typename?: 'Query', users: - | { __typename?: 'UsersResponseOk', result: Array<{ __typename?: 'User', id: string }> } - | { __typename?: 'ResponseError', error: ResponseErrorType } - }; - - export type UsersWithScalarInputQueryVariables = Exact<{ - from: any; - to?: any | null; - role?: UserRole | null; - }>; - - - export type UsersWithScalarInputQuery = { __typename?: 'Query', users: - | { __typename?: 'UsersResponseOk', result: Array<{ __typename: 'User' }> } - | { __typename: 'ResponseError' } - }; + export type UsersQuery = { __typename?: 'Query', users: Array<{ __typename?: 'User', id: string }> }; " `); }); - it('try different ways to generate enums', async () => { + it('test generating output enums in lists and inner field', async () => { const schema = buildSchema(/* GraphQL */ ` type Query { - user(id: ID!): User + user(id: ID!): User! } - type User { - id: ID! - name: String! - role: UserRole! + enum EnumRootLevel { + ENUM_A + ENUM_B } - enum UserRole { - ADMIN - CUSTOMER + enum EnumRootLevelArray { + ENUM_C + ENUM_D + } + + enum EnumInnerArray { + ENUM_E + ENUM_F + } + + type EnumsInner { + enumsDeep: [EnumInnerArray!]! + } + + type User { + enum: EnumRootLevel! + enums: [EnumRootLevelArray!]! + innerEnums: EnumsInner! } `); const document = parse(/* GraphQL */ ` query User($id: ID!) { user(id: $id) { - id - name - role + enum + enums + innerEnums { + enumsDeep + } } } `); - // string-literal - const resultStringLiteral = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'string-literal' })]); + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + extractAllFieldsToTypes: true, // Extracts all fields to separate types (similar to apollo-codegen behavior) + printFieldsOnNewLines: true, // Prints each field on a new line (similar to apollo-codegen behavior) + }), + ]); - expect(resultStringLiteral).toMatchInlineSnapshot(` + expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; - export type UserRole = - | 'ADMIN' - | 'CUSTOMER'; + export type EnumRootLevel = + | 'ENUM_A' + | 'ENUM_B'; - export type UserQueryVariables = Exact<{ - id: string; - }>; + export type EnumRootLevelArray = + | 'ENUM_C' + | 'ENUM_D'; + export type EnumInnerArray = + | 'ENUM_E' + | 'ENUM_F'; - export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; - " - `); + export type UserQuery_user_User_innerEnums_EnumsInner = { + __typename?: 'EnumsInner', + enumsDeep: Array + }; - // native-numeric - const resultNativeNumeric = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-numeric' })]); + export type UserQuery_user_User = { + __typename?: 'User', + enum: EnumRootLevel, + enums: Array, + innerEnums: UserQuery_user_User_innerEnums_EnumsInner + }; + + export type UserQuery_Query = { + __typename?: 'Query', + user: UserQuery_user_User + }; - expect(resultNativeNumeric).toMatchInlineSnapshot(` - "type Exact = { [K in keyof T]: T[K] }; - export enum UserRole { - Admin = 0, - Customer = 1 - } export type UserQueryVariables = Exact<{ id: string; }>; - export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; + export type UserQuery = UserQuery_Query; " `); + }); - // const - const resultConst = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'const' })]); + it('test overrdiding config.scalars', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + user(id: ID!): User + } - expect(resultConst).toMatchInlineSnapshot(` - "type Exact = { [K in keyof T]: T[K] }; - export const UserRole = { - Admin: 'ADMIN', - Customer: 'CUSTOMER' - } as const; + type User { + id: ID! + name: String! + } + `); + const document = parse(/* GraphQL */ ` + query User($id: ID!) { + user(id: $id) { + id + name + } + } + `); - export type UserRole = typeof UserRole[keyof typeof UserRole]; + const result = mergeOutputs([ + await plugin(schema, [{ document }], { scalars: { ID: 'string | number | boolean' } }), + ]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; export type UserQueryVariables = Exact<{ - id: string; + id: string | number | boolean; }>; - export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; + export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string | number | boolean, name: string } | null }; " `); + }); - // native-const - const resultNativeConst = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-const' })]); - - expect(resultNativeConst).toMatchInlineSnapshot(` - "type Exact = { [K in keyof T]: T[K] }; - export const enum UserRole { - Admin = 'ADMIN', - Customer = 'CUSTOMER' - }; + it('test render output enum from fragment in the same document', async () => { + const schema = buildSchema(/* GraphQL */ ` + enum RoleType { + ROLE_A + ROLE_B + } - export type UserQueryVariables = Exact<{ - id: string; - }>; + type User { + id: ID! + name: String! + role: RoleType + pictureUrl: String + } + type Query { + users: [User!]! + viewer: User! + } + `); + const document = parse(/* GraphQL */ ` + fragment UserBasic on User { + id + name + role + } - export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; - " + query GetUsersAndViewer { + users { + ...UserBasic + } + viewer { + ...UserBasic + } + } `); - // native - const resultNative = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + const result = mergeOutputs([await plugin(schema, [{ document }], {})]); - expect(resultNative).toMatchInlineSnapshot(` + expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; - export enum UserRole { - Admin = 'ADMIN', - Customer = 'CUSTOMER' - } + export type RoleType = + | 'ROLE_A' + | 'ROLE_B'; - export type UserQueryVariables = Exact<{ - id: string; - }>; + export type UserBasicFragment = { __typename?: 'User', id: string, name: string, role?: RoleType | null }; + export type GetUsersAndViewerQueryVariables = Exact<{ [key: string]: never; }>; - export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, name: string, role: UserRole } | null }; + + export type GetUsersAndViewerQuery = { __typename?: 'Query', users: Array<{ __typename?: 'User', id: string, name: string, role?: RoleType | null }>, viewer: { __typename?: 'User', id: string, name: string, role?: RoleType | null } }; " `); }); - it('test overrdiding config.scalars', async () => { + it('test render output enum from fragment in a separate document', async () => { const schema = buildSchema(/* GraphQL */ ` - type Query { - user(id: ID!): User + enum RoleType { + ROLE_A + ROLE_B } type User { id: ID! name: String! + role: RoleType + pictureUrl: String + } + + type Query { + users: [User!]! + viewer: User! } `); - const document = parse(/* GraphQL */ ` - query User($id: ID!) { - user(id: $id) { - id - name + + const documentWithFragment = parse(/* GraphQL */ ` + fragment UserBasic on User { + id + name + role + } + `); + + const documentMain = parse(/* GraphQL */ ` + query GetUsersAndViewer { + users { + ...UserBasic + } + viewer { + ...UserBasic } } `); const result = mergeOutputs([ - await plugin(schema, [{ document }], { scalars: { ID: 'string | number | boolean' } }), + await plugin(schema, [{ document: documentMain }, { document: documentWithFragment }], {}), ]); expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; - export type UserQueryVariables = Exact<{ - id: string | number | boolean; - }>; + export type RoleType = + | 'ROLE_A' + | 'ROLE_B'; + export type GetUsersAndViewerQueryVariables = Exact<{ [key: string]: never; }>; - export type UserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string | number | boolean, name: string } | null }; + + export type GetUsersAndViewerQuery = { __typename?: 'Query', users: Array<{ __typename?: 'User', id: string, name: string, role?: RoleType | null }>, viewer: { __typename?: 'User', id: string, name: string, role?: RoleType | null } }; + + export type UserBasicFragment = { __typename?: 'User', id: string, name: string, role?: RoleType | null }; " `); }); From d1fc7cc81e979f1d6c34ceb592fb8798b5569521 Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Thu, 4 Dec 2025 13:45:38 -0500 Subject: [PATCH 09/20] bugfixing after merge --- .../typescript/operations/src/visitor.ts | 19 +++++++++++ .../tests/ts-documents.standalone.spec.ts | 34 ++++++++++--------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 8dab920d00c..6e79104771e 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -42,6 +42,7 @@ import { ListTypeNode, NamedTypeNode, NonNullTypeNode, + ScalarTypeDefinitionNode, TypeInfo, visit, visitWithTypeInfo, @@ -210,6 +211,24 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< }); } + ScalarTypeDefinition(node: ScalarTypeDefinitionNode): string | null { + const scalarName = node.name.value; + + // Don't generate type aliases for built-in scalars + if (SCALARS[scalarName] || !this._usedNamedInputTypes[scalarName]) { + return null; + } + + // Check if a custom scalar mapping is provided in config + const scalarType = this.scalars?.[scalarName]?.input ?? 'any'; + + return new DeclarationBlock(this._declarationBlockConfig) + .export() + .asKind('type') + .withName(this.convertName(node)) + .withContent(scalarType).string; + } + InputObjectTypeDefinition(node: InputObjectTypeDefinitionNode): string | null { const inputTypeName = node.name.value; if (!this._usedNamedInputTypes[inputTypeName]) { 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 5a05362685a..2c1a105a875 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -161,12 +161,12 @@ describe('TypeScript Operations Plugin - Standalone', () => { id: ID! } - enum EnumRootLevel { + enum EnumRoot { ENUM_A ENUM_B } - enum EnumRootLevelArray { + enum EnumRootArray { ENUM_C ENUM_D } @@ -181,8 +181,8 @@ describe('TypeScript Operations Plugin - Standalone', () => { } input UsersInput { - enum: EnumRootLevel! - enums: [EnumRootLevelArray!]! + enum: EnumRoot! + enums: [EnumRootArray!]! innerEnums: EnumsInner! } `); @@ -198,11 +198,11 @@ describe('TypeScript Operations Plugin - Standalone', () => { expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; - export type EnumRootLevel = + export type EnumRoot = | 'ENUM_A' | 'ENUM_B'; - export type EnumRootLevelArray = + export type EnumRootArray = | 'ENUM_C' | 'ENUM_D'; @@ -215,8 +215,8 @@ describe('TypeScript Operations Plugin - Standalone', () => { }; export type UsersInput = { - enum: EnumRootLevel; - enums: Array; + enum: EnumRoot; + enums: Array; innerEnums: EnumsInner; }; @@ -236,12 +236,12 @@ describe('TypeScript Operations Plugin - Standalone', () => { user(id: ID!): User! } - enum EnumRootLevel { + enum EnumRoot { ENUM_A ENUM_B } - enum EnumRootLevelArray { + enum EnumRootArray { ENUM_C ENUM_D } @@ -256,8 +256,8 @@ describe('TypeScript Operations Plugin - Standalone', () => { } type User { - enum: EnumRootLevel! - enums: [EnumRootLevelArray!]! + enum: EnumRoot! + enums: [EnumRootArray!]! innerEnums: EnumsInner! } `); @@ -282,11 +282,11 @@ describe('TypeScript Operations Plugin - Standalone', () => { expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; - export type EnumRootLevel = + export type EnumRoot = | 'ENUM_A' | 'ENUM_B'; - export type EnumRootLevelArray = + export type EnumRootArray = | 'ENUM_C' | 'ENUM_D'; @@ -301,8 +301,8 @@ describe('TypeScript Operations Plugin - Standalone', () => { export type UserQuery_user_User = { __typename?: 'User', - enum: EnumRootLevel, - enums: Array, + enum: EnumRoot, + enums: Array, innerEnums: UserQuery_user_User_innerEnums_EnumsInner }; @@ -607,6 +607,8 @@ describe('TypeScript Operations Plugin - Standalone', () => { to: DateTime; role: UserRole; }; + + export type DateTime = any; " `); From 3954c3c308ae1af1c42009208190162dc198073f Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Thu, 11 Dec 2025 15:47:46 -0500 Subject: [PATCH 10/20] cleanup --- packages/plugins/typescript/operations/src/visitor.ts | 10 +++++++--- .../operations/tests/ts-documents.standalone.spec.ts | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 6e79104771e..516d17fc9e1 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -258,10 +258,9 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< } private getInputObjectOneOfDeclarationBlock(node: InputObjectTypeDefinitionNode): DeclarationBlock { - const declarationKind = (node.fields?.length || 0) === 1 ? 'type' : 'type'; return new DeclarationBlock(this._declarationBlockConfig) .export() - .asKind(declarationKind) + .asKind('type') .withName(this.convertName(node)) .withComment(node.description?.value) .withContent(`\n` + (node.fields || []).join('\n |')); @@ -338,6 +337,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< for (const field of Object.values(fields)) { const fieldType = getNamedType(field.type); if ( + fieldType && (fieldType instanceof GraphQLEnumType || fieldType instanceof GraphQLInputObjectType || fieldType instanceof GraphQLScalarType) && @@ -372,7 +372,8 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< foundInputType && (foundInputType instanceof GraphQLInputObjectType || foundInputType instanceof GraphQLScalarType || - foundInputType instanceof GraphQLEnumType) + foundInputType instanceof GraphQLEnumType) && + !usedInputTypes[namedTypeNode.name.value] ) { usedInputTypes[namedTypeNode.name.value] = foundInputType; if (foundInputType instanceof GraphQLInputObjectType) { @@ -388,6 +389,9 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< const typeInfo = new TypeInfo(schema); visit( documentNode, + // AST doesn’t include field types (they are defined in schema) - only names. + // TypeInfo is a stateful helper that tracks typing context while walking the AST + // visitWithTypeInfo wires that context into a visitor. visitWithTypeInfo(typeInfo, { Field: () => { const fieldType = typeInfo.getType(); 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 2c1a105a875..2c9cca7dce5 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -116,6 +116,8 @@ describe('TypeScript Operations Plugin - Standalone', () => { role: UserRole; }; + export type DateTime = any; + export type UserQueryVariables = Exact<{ id: string; }>; From 34a34db536cd092359fc34f51ffa4a282a8c53e5 Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Wed, 17 Dec 2025 16:10:42 -0500 Subject: [PATCH 11/20] cleanup --- packages/presets/graphql-modules/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/presets/graphql-modules/src/index.ts b/packages/presets/graphql-modules/src/index.ts index dbd6a07d0af..bcfab88a76a 100644 --- a/packages/presets/graphql-modules/src/index.ts +++ b/packages/presets/graphql-modules/src/index.ts @@ -15,7 +15,7 @@ export const preset: Types.OutputPreset = { const useTypeImports = getConfigValue(options?.config.useTypeImports, false) || false; const cwd = resolve(options.presetConfig.cwd || process.cwd()); - const importTypesNamespace = options.presetConfig.importTypesNamespace ?? 'Types'; + const importTypesNamespace = options.presetConfig.importTypesNamespace || 'Types'; if (!baseTypesPath) { throw new Error( From aa91f11cb0488d57a0b370a4083b100422525e32 Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Thu, 18 Dec 2025 14:38:16 -0500 Subject: [PATCH 12/20] fix snapshots --- packages/presets/client/tests/client-preset.enum.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/presets/client/tests/client-preset.enum.spec.ts b/packages/presets/client/tests/client-preset.enum.spec.ts index 89267a0f05d..58ac2f6f67f 100644 --- a/packages/presets/client/tests/client-preset.enum.spec.ts +++ b/packages/presets/client/tests/client-preset.enum.spec.ts @@ -135,6 +135,10 @@ describe('client-preset - Enum', () => { import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; type Exact = { [K in keyof T]: T[K] }; export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; + export type Shape = + | 'ROUND' + | 'SQUARE'; + export type ShapeQueryVariables = Exact<{ [key: string]: never; }>; From dbee7dbd0cdee93c7ae2f80d28615d4d4521f54e Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Thu, 18 Dec 2025 16:26:42 -0500 Subject: [PATCH 13/20] fix type errors in presets/client --- .../client/src/fragment-masking-plugin.ts | 72 ++++++++----------- .../client/tests/client-preset.spec.ts | 68 ++++++++---------- 2 files changed, 59 insertions(+), 81 deletions(-) diff --git a/packages/presets/client/src/fragment-masking-plugin.ts b/packages/presets/client/src/fragment-masking-plugin.ts index f3e69e3c6bf..1784eba6493 100644 --- a/packages/presets/client/src/fragment-masking-plugin.ts +++ b/packages/presets/client/src/fragment-masking-plugin.ts @@ -24,60 +24,48 @@ const defaultUnmaskFunctionName = 'useFragment'; const createUnmaskFunctionTypeDefinitions = (unmaskFunctionName = defaultUnmaskFunctionName) => [ `// return non-nullable if \`fragmentType\` is non-nullable -export function ${unmaskFunctionName}( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType;`, +export function ${unmaskFunctionName}>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf;`, `// return nullable if \`fragmentType\` is undefined -export function ${unmaskFunctionName}( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined;`, +export function ${unmaskFunctionName}>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined;`, `// return nullable if \`fragmentType\` is nullable -export function ${unmaskFunctionName}( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null;`, +export function ${unmaskFunctionName}>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null;`, `// return nullable if \`fragmentType\` is nullable or undefined -export function ${unmaskFunctionName}( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined;`, +export function ${unmaskFunctionName}>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined;`, `// return array of non-nullable if \`fragmentType\` is array of non-nullable -export function ${unmaskFunctionName}( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array;`, +export function ${unmaskFunctionName}>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>;`, `// return array of nullable if \`fragmentType\` is array of nullable -export function ${unmaskFunctionName}( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined;`, - - `// return readonly array of non-nullable if \`fragmentType\` is array of non-nullable -export function ${unmaskFunctionName}( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray;`, - - `// return readonly array of nullable if \`fragmentType\` is array of nullable -export function ${unmaskFunctionName}( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined;`, +export function ${unmaskFunctionName}>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined;`, ]; const createUnmaskFunction = (unmaskFunctionName = defaultUnmaskFunctionName) => ` ${createUnmaskFunctionTypeDefinitions(unmaskFunctionName).join('\n')} -export function ${unmaskFunctionName}( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | Array>> | ReadonlyArray>> | null | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function ${unmaskFunctionName}>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } `; @@ -96,7 +84,7 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } `; } @@ -115,7 +103,7 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } `; }; diff --git a/packages/presets/client/tests/client-preset.spec.ts b/packages/presets/client/tests/client-preset.spec.ts index 403e58e3e4f..c8156146bf5 100644 --- a/packages/presets/client/tests/client-preset.spec.ts +++ b/packages/presets/client/tests/client-preset.spec.ts @@ -813,49 +813,39 @@ export * from "./gql";`); : never; // return non-nullable if \`fragmentType\` is non-nullable - export function iLikeTurtles( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> - ): TType; + export function iLikeTurtles>( + _documentNode: F, + fragmentType: FragmentType + ): ResultOf; // return nullable if \`fragmentType\` is undefined - export function iLikeTurtles( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined - ): TType | undefined; + export function iLikeTurtles>( + _documentNode: F, + fragmentType: FragmentType | undefined + ): ResultOf | undefined; // return nullable if \`fragmentType\` is nullable - export function iLikeTurtles( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null - ): TType | null; + export function iLikeTurtles>( + _documentNode: F, + fragmentType: FragmentType | null + ): ResultOf | null; // return nullable if \`fragmentType\` is nullable or undefined - export function iLikeTurtles( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined - ): TType | null | undefined; + export function iLikeTurtles>( + _documentNode: F, + fragmentType: FragmentType | null | undefined + ): ResultOf | null | undefined; // return array of non-nullable if \`fragmentType\` is array of non-nullable - export function iLikeTurtles( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> - ): Array; + export function iLikeTurtles>( + _documentNode: F, + fragmentType: ReadonlyArray> + ): ReadonlyArray>; // return array of nullable if \`fragmentType\` is array of nullable - export function iLikeTurtles( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined - ): Array | null | undefined; - // return readonly array of non-nullable if \`fragmentType\` is array of non-nullable - export function iLikeTurtles( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> - ): ReadonlyArray; - // return readonly array of nullable if \`fragmentType\` is array of nullable - export function iLikeTurtles( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined - ): ReadonlyArray | null | undefined; - export function iLikeTurtles( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | Array>> | ReadonlyArray>> | null | undefined - ): TType | Array | ReadonlyArray | null | undefined { + export function iLikeTurtles>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined + ): ReadonlyArray> | null | undefined; + export function iLikeTurtles>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined + ): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -880,7 +870,7 @@ export * from "./gql";`); const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } " `); From db05491e552cb542f4349568bf1c0ed9688ed5f5 Mon Sep 17 00:00:00 2001 From: Igor Kusakov Date: Thu, 18 Dec 2025 16:30:06 -0500 Subject: [PATCH 14/20] updated tests/examples --- .../gql/fragment-masking.ts | 73 ++++++++----------- .../gql/fragment-masking.ts | 73 ++++++++----------- .../gql/fragment-masking.ts | 73 ++++++++----------- .../graphql/fragment-masking.ts | 73 ++++++++----------- .../src/gql/fragment-masking.ts | 73 ++++++++----------- .../src/gql/fragment-masking.ts | 73 ++++++++----------- .../src/gql/fragment-masking.ts | 73 ++++++++----------- .../apollo-client/src/gql/fragment-masking.ts | 73 ++++++++----------- .../http-executor/src/gql/fragment-masking.ts | 73 ++++++++----------- .../src/gql/fragment-masking.ts | 73 ++++++++----------- .../react/urql/src/gql/fragment-masking.ts | 73 ++++++++----------- .../src/gql/fragment-masking.ts | 73 ++++++++----------- .../src/gql/fragment-masking.ts | 73 ++++++++----------- .../src/gql/fragment-masking.ts | 73 ++++++++----------- .../src/gql/fragment-masking.ts | 73 ++++++++----------- .../vite-react-ts/src/gql/fragment-masking.ts | 73 ++++++++----------- .../src/gql/fragment-masking.ts | 73 ++++++++----------- examples/vue/urql/src/gql/fragment-masking.ts | 73 ++++++++----------- .../vue/villus/src/gql/fragment-masking.ts | 73 ++++++++----------- .../yoga-tests/src/gql/fragment-masking.ts | 73 ++++++++----------- 20 files changed, 580 insertions(+), 880 deletions(-) diff --git a/dev-test/gql-tag-operations-masking/gql/fragment-masking.ts b/dev-test/gql-tag-operations-masking/gql/fragment-masking.ts index a6b3407f590..4bfad5cd554 100644 --- a/dev-test/gql-tag-operations-masking/gql/fragment-masking.ts +++ b/dev-test/gql-tag-operations-masking/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/dev-test/gql-tag-operations-urql/gql/fragment-masking.ts b/dev-test/gql-tag-operations-urql/gql/fragment-masking.ts index a6b3407f590..4bfad5cd554 100644 --- a/dev-test/gql-tag-operations-urql/gql/fragment-masking.ts +++ b/dev-test/gql-tag-operations-urql/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/dev-test/gql-tag-operations/gql/fragment-masking.ts b/dev-test/gql-tag-operations/gql/fragment-masking.ts index a6b3407f590..4bfad5cd554 100644 --- a/dev-test/gql-tag-operations/gql/fragment-masking.ts +++ b/dev-test/gql-tag-operations/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/dev-test/gql-tag-operations/graphql/fragment-masking.ts b/dev-test/gql-tag-operations/graphql/fragment-masking.ts index a6b3407f590..4bfad5cd554 100644 --- a/dev-test/gql-tag-operations/graphql/fragment-masking.ts +++ b/dev-test/gql-tag-operations/graphql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/persisted-documents-string-mode/src/gql/fragment-masking.ts b/examples/persisted-documents-string-mode/src/gql/fragment-masking.ts index dedac7e7f7e..22e69da9617 100644 --- a/examples/persisted-documents-string-mode/src/gql/fragment-masking.ts +++ b/examples/persisted-documents-string-mode/src/gql/fragment-masking.ts @@ -12,54 +12,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -80,5 +65,5 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/persisted-documents/src/gql/fragment-masking.ts b/examples/persisted-documents/src/gql/fragment-masking.ts index c469b9c617c..0a186994ef8 100644 --- a/examples/persisted-documents/src/gql/fragment-masking.ts +++ b/examples/persisted-documents/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/react/apollo-client-defer/src/gql/fragment-masking.ts b/examples/react/apollo-client-defer/src/gql/fragment-masking.ts index c469b9c617c..0a186994ef8 100644 --- a/examples/react/apollo-client-defer/src/gql/fragment-masking.ts +++ b/examples/react/apollo-client-defer/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/react/apollo-client/src/gql/fragment-masking.ts b/examples/react/apollo-client/src/gql/fragment-masking.ts index c469b9c617c..0a186994ef8 100644 --- a/examples/react/apollo-client/src/gql/fragment-masking.ts +++ b/examples/react/apollo-client/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/react/http-executor/src/gql/fragment-masking.ts b/examples/react/http-executor/src/gql/fragment-masking.ts index c469b9c617c..0a186994ef8 100644 --- a/examples/react/http-executor/src/gql/fragment-masking.ts +++ b/examples/react/http-executor/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/react/tanstack-react-query/src/gql/fragment-masking.ts b/examples/react/tanstack-react-query/src/gql/fragment-masking.ts index dedac7e7f7e..22e69da9617 100644 --- a/examples/react/tanstack-react-query/src/gql/fragment-masking.ts +++ b/examples/react/tanstack-react-query/src/gql/fragment-masking.ts @@ -12,54 +12,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -80,5 +65,5 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/react/urql/src/gql/fragment-masking.ts b/examples/react/urql/src/gql/fragment-masking.ts index dedac7e7f7e..22e69da9617 100644 --- a/examples/react/urql/src/gql/fragment-masking.ts +++ b/examples/react/urql/src/gql/fragment-masking.ts @@ -12,54 +12,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -80,5 +65,5 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/typescript-esm/src/gql/fragment-masking.ts b/examples/typescript-esm/src/gql/fragment-masking.ts index a6b3407f590..4bfad5cd554 100644 --- a/examples/typescript-esm/src/gql/fragment-masking.ts +++ b/examples/typescript-esm/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/typescript-graphql-request/src/gql/fragment-masking.ts b/examples/typescript-graphql-request/src/gql/fragment-masking.ts index dedac7e7f7e..22e69da9617 100644 --- a/examples/typescript-graphql-request/src/gql/fragment-masking.ts +++ b/examples/typescript-graphql-request/src/gql/fragment-masking.ts @@ -12,54 +12,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -80,5 +65,5 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/vite/vite-react-cts/src/gql/fragment-masking.ts b/examples/vite/vite-react-cts/src/gql/fragment-masking.ts index c469b9c617c..0a186994ef8 100644 --- a/examples/vite/vite-react-cts/src/gql/fragment-masking.ts +++ b/examples/vite/vite-react-cts/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/vite/vite-react-mts/src/gql/fragment-masking.ts b/examples/vite/vite-react-mts/src/gql/fragment-masking.ts index c469b9c617c..0a186994ef8 100644 --- a/examples/vite/vite-react-mts/src/gql/fragment-masking.ts +++ b/examples/vite/vite-react-mts/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/vite/vite-react-ts/src/gql/fragment-masking.ts b/examples/vite/vite-react-ts/src/gql/fragment-masking.ts index c469b9c617c..0a186994ef8 100644 --- a/examples/vite/vite-react-ts/src/gql/fragment-masking.ts +++ b/examples/vite/vite-react-ts/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/vue/apollo-composable/src/gql/fragment-masking.ts b/examples/vue/apollo-composable/src/gql/fragment-masking.ts index a97fd9b635e..024a8ba0caa 100644 --- a/examples/vue/apollo-composable/src/gql/fragment-masking.ts +++ b/examples/vue/apollo-composable/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/vue/urql/src/gql/fragment-masking.ts b/examples/vue/urql/src/gql/fragment-masking.ts index a97fd9b635e..024a8ba0caa 100644 --- a/examples/vue/urql/src/gql/fragment-masking.ts +++ b/examples/vue/urql/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/vue/villus/src/gql/fragment-masking.ts b/examples/vue/villus/src/gql/fragment-masking.ts index a97fd9b635e..024a8ba0caa 100644 --- a/examples/vue/villus/src/gql/fragment-masking.ts +++ b/examples/vue/villus/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } diff --git a/examples/yoga-tests/src/gql/fragment-masking.ts b/examples/yoga-tests/src/gql/fragment-masking.ts index c469b9c617c..0a186994ef8 100644 --- a/examples/yoga-tests/src/gql/fragment-masking.ts +++ b/examples/yoga-tests/src/gql/fragment-masking.ts @@ -13,54 +13,39 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType +): ResultOf; // return nullable if `fragmentType` is undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | undefined -): TType | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | undefined +): ResultOf | undefined; // return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null -): TType | null; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null +): ResultOf | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | null | undefined +): ResultOf | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> -): Array; +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> +): ReadonlyArray>; // return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: Array>> | null | undefined -): Array | null | undefined; -// return readonly array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray; -// return readonly array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined; -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | Array>> - | ReadonlyArray>> - | null - | undefined -): TType | Array | ReadonlyArray | null | undefined { +export function useFragment>( + _documentNode: F, + fragmentType: ReadonlyArray> | null | undefined +): ReadonlyArray> | null | undefined; +export function useFragment>( + _documentNode: F, + fragmentType: FragmentType | ReadonlyArray> | null | undefined +): ResultOf | ReadonlyArray> | null | undefined { return fragmentType as any; } @@ -84,5 +69,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every(field => data && field in data); + return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); } From e89b35335225c6dd11a7c5ba96a302252ea2214a Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Fri, 19 Dec 2025 23:35:53 +1100 Subject: [PATCH 15/20] Add standalone.input.spec.ts and update standalone tests to TDD --- ...-documents.standalone.import-types.spec.ts | 20 +- .../ts-documents.standalone.input.spec.ts | 368 ++++++++++++++++++ .../tests/ts-documents.standalone.spec.ts | 24 +- 3 files changed, 386 insertions(+), 26 deletions(-) create mode 100644 packages/plugins/typescript/operations/tests/ts-documents.standalone.input.spec.ts 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 index df9c23ebc82..7063e9d09ff 100644 --- 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 @@ -108,16 +108,14 @@ describe('TypeScript Operations Plugin - Import Types', () => { type Exact = { [K in keyof T]: T[K] }; export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; /** UsersInput Description */ - export type UsersInput = { + type UsersInput = { /** UsersInput from */ - from: DateTime; + from?: any; /** UsersInput to */ - to: DateTime; - role: UserRole; + to?: any; + role?: UserRole | null | undefined; }; - export type DateTime = any; - export type UserQueryVariables = Exact<{ id: string; }>; @@ -254,16 +252,14 @@ describe('TypeScript Operations Plugin - Import Types', () => { type Exact = { [K in keyof T]: T[K] }; export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; /** UsersInput Description */ - export type UsersInput = { + type UsersInput = { /** UsersInput from */ - from: DateTime; + from?: any; /** UsersInput to */ - to: DateTime; - role: UserRole; + to?: any; + role?: UserRole | null | undefined; }; - export type DateTime = any; - export type UserQueryVariables = Exact<{ id: string; }>; diff --git a/packages/plugins/typescript/operations/tests/ts-documents.standalone.input.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.standalone.input.spec.ts new file mode 100644 index 00000000000..c10cfe18f9c --- /dev/null +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.input.spec.ts @@ -0,0 +1,368 @@ +import { mergeOutputs } from '@graphql-codegen/plugin-helpers'; +import { validateTs } from '@graphql-codegen/testing'; +import { buildSchema, parse } from 'graphql'; +import { plugin } from '../src/index.js'; + +describe('TypeScript Operations Plugin - Input', () => { + it('generates nested input correctly', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + users(input: UsersInput!): [User!]! + } + + type ResponseError { + error: ResponseErrorType! + } + + enum ResponseErrorType { + NOT_FOUND + INPUT_VALIDATION_ERROR + FORBIDDEN_ERROR + UNEXPECTED_ERROR + } + + type User { + id: ID! + ageRange1: [Int] + ageRange2: [Int]! + ageRange3: [Int!] + ageRange4: [Int!]! + } + + "UserRole Description" + enum UserRole { + "UserRole ADMIN" + ADMIN + "UserRole CUSTOMER" + CUSTOMER + } + + "UsersInput Description" + input UsersInput { + "UsersInput from" + from: DateTime + "UsersInput to" + to: DateTime + timezone: TimeZone + role: UserRole + ageRange1: [Int] + ageRange2: [Int]! + ageRange3: [Int!] + ageRange4: [Int!]! + bestFriend: UsersBestFriendInput + nestedInput: UsersInput + } + + input UsersBestFriendInput { + name: String + } + + scalar DateTime + scalar TimeZone + `); + const document = parse(/* GraphQL */ ` + query UsersWithScalarInput($inputNonNullable: UsersInput!, $inputNullable: UsersInput) { + users(input: $inputNonNullable) { + ageRange1 + ageRange2 + ageRange3 + ageRange4 + } + } + `); + + const result = mergeOutputs([ + await plugin( + schema, + [{ document }], + { + scalars: { + DateTime: 'Date', + }, + }, + { outputFile: '' } + ), + ]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; + /** UserRole Description */ + export type UserRole = + /** UserRole ADMIN */ + | 'ADMIN' + /** UserRole CUSTOMER */ + | 'CUSTOMER'; + + /** UsersInput Description */ + type UsersInput = { + /** UsersInput from */ + from?: Date | null | undefined; + /** UsersInput to */ + to?: Date | null | undefined; + timezone?: any; + role?: UserRole | null | undefined; + ageRange1?: Array | null | undefined; + ageRange2: Array; + ageRange3?: Array | null | undefined; + ageRange4: Array; + bestFriend?: UsersBestFriendInput | null | undefined; + nestedInput?: UsersInput | null | undefined; + }; + + type UsersBestFriendInput = { + name?: string | null | undefined; + }; + + export type UsersWithScalarInputQueryVariables = Exact<{ + inputNonNullable: UsersInput; + inputNullable?: UsersInput | null; + }>; + + + export type UsersWithScalarInputQuery = { __typename?: 'Query', users: Array<{ __typename?: 'User', ageRange1: Array | null, ageRange2: Array, ageRange3: Array | null, ageRange4: Array }> }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('generates readonly input when immutableTypes:true', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + users(input: UsersInput!): [User!]! + } + + type ResponseError { + error: ResponseErrorType! + } + + enum ResponseErrorType { + NOT_FOUND + INPUT_VALIDATION_ERROR + FORBIDDEN_ERROR + UNEXPECTED_ERROR + } + + type User { + id: ID! + ageRange1: [Int] + ageRange2: [Int]! + ageRange3: [Int!] + ageRange4: [Int!]! + } + + "UserRole Description" + enum UserRole { + "UserRole ADMIN" + ADMIN + "UserRole CUSTOMER" + CUSTOMER + } + + "UsersInput Description" + input UsersInput { + "UsersInput from" + from: DateTime + "UsersInput to" + to: DateTime + timezone: TimeZone + role: UserRole + ageRange1: [Int] + ageRange2: [Int]! + ageRange3: [Int!] + ageRange4: [Int!]! + bestFriend: UsersBestFriendInput + nestedInput: UsersInput + } + + input UsersBestFriendInput { + name: String + } + + scalar DateTime + scalar TimeZone + `); + const document = parse(/* GraphQL */ ` + query UsersWithScalarInput($inputNonNullable: UsersInput!, $inputNullable: UsersInput) { + users(input: $inputNonNullable) { + ageRange1 + ageRange2 + ageRange3 + ageRange4 + } + } + `); + + const result = mergeOutputs([ + await plugin( + schema, + [{ document }], + { + scalars: { + DateTime: 'Date', + }, + immutableTypes: true, + }, + { outputFile: '' } + ), + ]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; + /** UserRole Description */ + export type UserRole = + /** UserRole ADMIN */ + | 'ADMIN' + /** UserRole CUSTOMER */ + | 'CUSTOMER'; + + /** UsersInput Description */ + type UsersInput = { + /** UsersInput from */ + readonly from?: Date | null | undefined; + /** UsersInput to */ + readonly to?: Date | null | undefined; + readonly timezone?: any; + readonly role?: UserRole | null | undefined; + readonly ageRange1?: Array | null | undefined; + readonly ageRange2: Array; + readonly ageRange3?: Array | null | undefined; + readonly ageRange4: Array; + readonly bestFriend?: UsersBestFriendInput | null | undefined; + readonly nestedInput?: UsersInput | null | undefined; + }; + + type UsersBestFriendInput = { + readonly name?: string | null | undefined; + }; + + export type UsersWithScalarInputQueryVariables = Exact<{ + inputNonNullable: UsersInput; + inputNullable?: UsersInput | null; + }>; + + + export type UsersWithScalarInputQuery = { readonly __typename?: 'Query', readonly users: ReadonlyArray<{ readonly __typename?: 'User', readonly ageRange1: ReadonlyArray | null, readonly ageRange2: ReadonlyArray, readonly ageRange3: ReadonlyArray | null, readonly ageRange4: ReadonlyArray }> }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('generates @oneOf input correctly', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + users(input: UsersInput!): [User!]! + } + + type ResponseError { + error: ResponseErrorType! + } + + enum ResponseErrorType { + NOT_FOUND + INPUT_VALIDATION_ERROR + FORBIDDEN_ERROR + UNEXPECTED_ERROR + } + + type User { + id: ID! + ageRange1: [Int] + ageRange2: [Int]! + ageRange3: [Int!] + ageRange4: [Int!]! + } + + "UserRole Description" + enum UserRole { + "UserRole ADMIN" + ADMIN + "UserRole CUSTOMER" + CUSTOMER + } + + "UsersInput Description" + input UsersInput @oneOf { + "UsersInput from" + from: DateTime + "UsersInput to" + to: DateTime + timezone: TimeZone + role: UserRole + ageRange1: [Int] + ageRange3: [Int!] + bestFriend: UsersBestFriendInput + nestedInput: UsersInput + } + + input UsersBestFriendInput { + name: String + } + + scalar DateTime + scalar TimeZone + `); + const document = parse(/* GraphQL */ ` + query Users($inputNonNullable: UsersInput!, $inputNullable: UsersInput) { + users(input: $inputNonNullable) { + __typename + } + } + `); + + const result = mergeOutputs([ + await plugin( + schema, + [{ document }], + { + scalars: { + DateTime: 'Date', + }, + }, + { outputFile: '' } + ), + ]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; + /** UserRole Description */ + export type UserRole = + /** UserRole ADMIN */ + | 'ADMIN' + /** UserRole CUSTOMER */ + | 'CUSTOMER'; + + /** UsersInput Description */ + type UsersInput = + { /** UsersInput from */ + from: Date; to?: never; timezone?: never; role?: never; ageRange1?: never; ageRange3?: never; bestFriend?: never; nestedInput?: never; } + | { from?: never; /** UsersInput to */ + to: Date; timezone?: never; role?: never; ageRange1?: never; ageRange3?: never; bestFriend?: never; nestedInput?: never; } + | { from?: never; to?: never; timezone: any; role?: never; ageRange1?: never; ageRange3?: never; bestFriend?: never; nestedInput?: never; } + | { from?: never; to?: never; timezone?: never; role: UserRole; ageRange1?: never; ageRange3?: never; bestFriend?: never; nestedInput?: never; } + | { from?: never; to?: never; timezone?: never; role?: never; ageRange1: Array; ageRange3?: never; bestFriend?: never; nestedInput?: never; } + | { from?: never; to?: never; timezone?: never; role?: never; ageRange1?: never; ageRange3: Array; bestFriend?: never; nestedInput?: never; } + | { from?: never; to?: never; timezone?: never; role?: never; ageRange1?: never; ageRange3?: never; bestFriend: UsersBestFriendInput; nestedInput?: never; } + | { from?: never; to?: never; timezone?: never; role?: never; ageRange1?: never; ageRange3?: never; bestFriend?: never; nestedInput: UsersInput; }; + + type UsersBestFriendInput = { + name?: string | null | undefined; + }; + + export type UsersQueryVariables = Exact<{ + inputNonNullable: UsersInput; + inputNullable?: UsersInput | null; + }>; + + + export type UsersQuery = { __typename?: 'Query', users: Array<{ __typename: 'User' }> }; + " + `); + + 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 29ae5bfe094..c2d4c58e33c 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -111,16 +111,14 @@ describe('TypeScript Operations Plugin - Standalone', () => { | 'CUSTOMER'; /** UsersInput Description */ - export type UsersInput = { + type UsersInput = { /** UsersInput from */ - from: DateTime; + from?: any; /** UsersInput to */ - to: DateTime; - role: UserRole; + to?: any; + role?: UserRole | null | undefined; }; - export type DateTime = any; - export type UserQueryVariables = Exact<{ id: string; }>; @@ -216,11 +214,11 @@ describe('TypeScript Operations Plugin - Standalone', () => { | 'ENUM_E' | 'ENUM_F'; - export type EnumsInner = { + type EnumsInner = { enumsDeep: Array; }; - export type UsersInput = { + type UsersInput = { enum: EnumRoot; enums: Array; innerEnums: EnumsInner; @@ -620,15 +618,13 @@ describe('TypeScript Operations Plugin - Standalone', () => { | 'CUSTOMER'; /** UsersInput Description */ - export type UsersInput = { + type UsersInput = { /** UsersInput from */ - from: DateTime; + from?: any; /** UsersInput to */ - to: DateTime; - role: UserRole; + to?: any; + role?: UserRole | null | undefined; }; - - export type DateTime = any; " `); From 3ab45f6d76893d9d9e4fad5bcf9cd6777034cde5 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Sun, 21 Dec 2025 20:53:37 +1100 Subject: [PATCH 16/20] Update operations/visitor.ts to satisfy tests --- .../typescript/operations/src/visitor.ts | 306 ++++++++++++------ 1 file changed, 202 insertions(+), 104 deletions(-) diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 656ad6c6d1c..0c23975b24d 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -20,7 +20,7 @@ import { PreResolveTypesProcessor, SelectionSetProcessorConfig, SelectionSetToObject, - transformComment, + getNodeComment, wrapTypeWithModifiers, } from '@graphql-codegen/visitor-plugin-common'; import autoBind from 'auto-bind'; @@ -38,11 +38,9 @@ import { InputValueDefinitionNode, isEnumType, Kind, - ListTypeNode, - NamedTypeNode, - NonNullTypeNode, - ScalarTypeDefinitionNode, + type TypeDefinitionNode, TypeInfo, + type TypeNode, visit, visitWithTypeInfo, } from 'graphql'; @@ -62,7 +60,12 @@ export interface TypeScriptDocumentsParsedConfig extends ParsedDocumentsConfig { enumValues: ParsedEnumValuesMap; } -type UsedNamedInputTypes = Record; +type UsedNamedInputTypes = Record< + string, + | { type: 'GraphQLScalarType'; node: GraphQLScalarType; tsType: string } + | { type: 'GraphQLEnumType'; node: GraphQLEnumType; tsType: string } + | { type: 'GraphQLInputObjectType'; node: GraphQLInputObjectType; tsType: string } +>; export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< TypeScriptDocumentsPluginConfig, @@ -211,24 +214,6 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< }); } - ScalarTypeDefinition(node: ScalarTypeDefinitionNode): string | null { - const scalarName = node.name.value; - - // Don't generate type aliases for built-in scalars - if (SCALARS[scalarName] || !this._usedNamedInputTypes[scalarName]) { - return null; - } - - // Check if a custom scalar mapping is provided in config - const scalarType = this.scalars?.[scalarName]?.input ?? 'any'; - - return new DeclarationBlock(this._declarationBlockConfig) - .export() - .asKind('type') - .withName(this.convertName(node)) - .withContent(scalarType).string; - } - InputObjectTypeDefinition(node: InputObjectTypeDefinitionNode): string | null { const inputTypeName = node.name.value; if (!this._usedNamedInputTypes[inputTypeName]) { @@ -236,82 +221,177 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< } if (isOneOfInputObjectType(this._schema.getType(inputTypeName))) { - return this.getInputObjectOneOfDeclarationBlock(node).string; + return new DeclarationBlock(this._declarationBlockConfig) + .asKind('type') + .withName(this.convertName(node)) + .withComment(node.description?.value) + .withContent(`\n` + (node.fields || []).join('\n |')).string; } - return this.getInputObjectDeclarationBlock(node).string; - } - - InputValueDefinition(node: InputValueDefinitionNode): string { - const comment = transformComment(node.description?.value || '', 1); - const type: string = node.type as any as string; - return comment + indent(`${node.name.value}: ${type};`); - } - - private getInputObjectDeclarationBlock(node: InputObjectTypeDefinitionNode): DeclarationBlock { return new DeclarationBlock(this._declarationBlockConfig) - .export() .asKind('type') .withName(this.convertName(node)) .withComment(node.description?.value) - .withBlock((node.fields || []).join('\n')); + .withBlock((node.fields || []).join('\n')).string; } - private getInputObjectOneOfDeclarationBlock(node: InputObjectTypeDefinitionNode): DeclarationBlock { - return new DeclarationBlock(this._declarationBlockConfig) - .export() - .asKind('type') - .withName(this.convertName(node)) - .withComment(node.description?.value) - .withContent(`\n` + (node.fields || []).join('\n |')); - } - - private isValidVisit(ancestors: any): boolean { - const currentVisitContext = this.getVisitorKindContextFromAncestors(ancestors); - const isVisitingInputType = currentVisitContext.includes(Kind.INPUT_OBJECT_TYPE_DEFINITION); - const isVisitingEnumType = currentVisitContext.includes(Kind.ENUM_TYPE_DEFINITION); - - return isVisitingInputType || isVisitingEnumType; - } - - NamedType(node: NamedTypeNode, _key: any, _parent: any, _path: any, ancestors: any): string | undefined { - if (!this.isValidVisit(ancestors)) { - return undefined; - } - - const schemaType = this._schema.getType(node.name.value); - - if (schemaType instanceof GraphQLScalarType) { - const inputType = this.scalars?.[node.name.value]?.input ?? SCALARS[node.name.value] ?? 'any'; - if (inputType === 'any' && node.name.value) { - return node.name.value; + InputValueDefinition( + node: InputValueDefinitionNode, + _key?: number | string, + _parent?: any, + _path?: Array, + ancestors?: Array + ): string { + const oneOfDetails = (function parseOneOf( + schema: GraphQLSchema + ): { isOneOfInputValue: true; realParentDef: TypeDefinitionNode } | { isOneOfInputValue: false } { + const realParentDef = ancestors?.[ancestors.length - 1]; + if (realParentDef) { + const parentType = schema.getType(realParentDef.name.value); + if (isOneOfInputObjectType(parentType)) { + if (node.type.kind === Kind.NON_NULL_TYPE) { + throw new Error( + 'Fields on an input object type can not be non-nullable. It seems like the schema was not validated.' + ); + } + return { isOneOfInputValue: true, realParentDef }; + } } + return { isOneOfInputValue: false }; + })(this._schema); + + // 1. Flatten GraphQL type nodes to make it easier to turn into string + // GraphQL type nodes may have `NonNullType` type before each `ListType` or `NamedType` + // This make it a bit harder to know whether a `ListType` or `Namedtype` is nullable without looking at the node before it. + // Flattening it into an array where the nullability is in `ListType` and `NamedType` makes it easier to code, + // + // So, we recursively call `collectAndFlattenTypeNodes` to handle the following scenarios: + // - [Thing] + // - [Thing!] + // - [Thing]! + // - [Thing!]! + const typeNodes: Array< + { type: 'ListType'; isNonNullable: boolean } | { type: 'NamedType'; isNonNullable: boolean; name: string } + > = []; + (function collectAndFlattenTypeNodes({ + currentTypeNode, + isPreviousNodeNonNullable, + }: { + currentTypeNode: TypeNode; + isPreviousNodeNonNullable: boolean; + }): void { + if (currentTypeNode.kind === Kind.NON_NULL_TYPE) { + const nextTypeNode = currentTypeNode.type; + collectAndFlattenTypeNodes({ currentTypeNode: nextTypeNode, isPreviousNodeNonNullable: true }); + } else if (currentTypeNode.kind === Kind.LIST_TYPE) { + typeNodes.push({ type: 'ListType', isNonNullable: isPreviousNodeNonNullable }); + + const nextTypeNode = currentTypeNode.type; + collectAndFlattenTypeNodes({ currentTypeNode: nextTypeNode, isPreviousNodeNonNullable: false }); + } else if (currentTypeNode.kind === Kind.NAMED_TYPE) { + typeNodes.push({ + type: 'NamedType', + isNonNullable: isPreviousNodeNonNullable, + name: currentTypeNode.name.value, + }); + } + })({ + currentTypeNode: node.type, + isPreviousNodeNonNullable: oneOfDetails.isOneOfInputValue, // If the InputValue is part of @oneOf input, we treat it as non-null (even if it must be null in the schema) + }); - return inputType; - } - - if (schemaType instanceof GraphQLEnumType || schemaType instanceof GraphQLInputObjectType) { - return this.convertName(node.name.value); - } + // 2. Generate the type of a TypeScript field declaration + // e.g. `field?: string`, then the `string` is the `typePart` + let typePart: string = ''; + // We call `.reverse()` here to get the base type node first + for (const typeNode of typeNodes.reverse()) { + if (typeNode.type === 'NamedType') { + const usedInputType = this._usedNamedInputTypes[typeNode.name]; + if (!usedInputType) { + continue; + } - return node.name.value; - } + typePart = usedInputType.tsType; // If the schema is correct, when reversing typeNodes, the first node would be `NamedType`, which means we can safely set it as the base for typePart + if (usedInputType.tsType !== 'any' && !typeNode.isNonNullable) { + typePart += ' | null | undefined'; + } + continue; + } - ListType(node: ListTypeNode, _key: any, _parent: any, _path: any, ancestors: any): string | undefined { - if (!this.isValidVisit(ancestors)) { - return undefined; + if (typeNode.type === 'ListType') { + typePart = `Array<${typePart}>`; + if (!typeNode.isNonNullable) { + typePart += ' | null | undefined'; + } + } } - const listModifier = this.config.immutableTypes ? 'ReadonlyArray' : 'Array'; - return `${listModifier}<${node.type}>`; - } - - NonNullType(node: NonNullTypeNode, _key: any, _parent: any, _path: any, ancestors: any): string | undefined { - if (!this.isValidVisit(ancestors)) { - return undefined; + // TODO: eddeee888 check if we want to support `directiveArgumentAndInputFieldMappings` for operations + // if (node.directives && this.config.directiveArgumentAndInputFieldMappings) { + // typePart = + // getDirectiveOverrideType({ + // directives: node.directives, + // directiveArgumentAndInputFieldMappings: this.config.directiveArgumentAndInputFieldMappings, + // }) || typePart; + // } + + const addOptionalSign = + !oneOfDetails.isOneOfInputValue && + !this.config.avoidOptionals.inputValue && + (node.type.kind !== Kind.NON_NULL_TYPE || + (!this.config.avoidOptionals.defaultValue && node.defaultValue !== undefined)); + + // 3. Generate the keyPart of the TypeScript field declaration + // e.g. `field?: string`, then the `field?` is the `keyPart` + const keyPart = `${node.name.value}${addOptionalSign ? '?' : ''}`; + + // 4. other parts of TypeScript field declaration + const commentPart = getNodeComment(node); + const readonlyPart = this.config.immutableTypes ? 'readonly ' : ''; + + const currentInputValue = commentPart + indent(`${readonlyPart}${keyPart}: ${typePart};`); + + // 5. Check if field is part of `@oneOf` input type + // If yes, we must generate a union member where the current inputValue must be provieded, and the others are not + // e.g. + // ```graphql + // input UserInput { + // byId: ID + // byEmail: String + // byLegacyId: ID + // } + // ``` + // + // Then, the generated type is: + // ```ts + // type UserInput = + // | { byId: string | number; byEmail?: never; byLegacyId?: never } + // | { byId?: never; byEmail: string; byLegacyId?: never } + // | { byId?: never; byEmail?: never; byLegacyId: string | number } + // ``` + + if (oneOfDetails.isOneOfInputValue) { + const parentType = this._schema.getType(oneOfDetails.realParentDef.name.value); + if (isOneOfInputObjectType(parentType)) { + if (node.type.kind === Kind.NON_NULL_TYPE) { + throw new Error( + 'Fields on an input object type can not be non-nullable. It seems like the schema was not validated.' + ); + } + const fieldParts: Array = []; + for (const fieldName of Object.keys(parentType.getFields())) { + if (fieldName === node.name.value) { + fieldParts.push(currentInputValue); + continue; + } + fieldParts.push(`${readonlyPart}${fieldName}?: never;`); + } + return indent(`{ ${fieldParts.join(' ')} }`); + } } - return node.type as any as string | undefined; + // If field is not part of @oneOf input type, then it's a input value, just return as-is + return currentInputValue; } public getImports(): Array { @@ -359,22 +439,40 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< return `Exact<${variablesBlock === '{}' ? `{ [key: string]: never; }` : variablesBlock}>${extraType}`; } - private collectInnerTypesRecursively(type: GraphQLInputObjectType, usedInputTypes: UsedNamedInputTypes): void { - const fields = type.getFields(); + private collectInnerTypesRecursively(node: GraphQLNamedInputType, usedInputTypes: UsedNamedInputTypes): void { + if (usedInputTypes[node.name]) { + return; + } + + if (node instanceof GraphQLEnumType) { + usedInputTypes[node.name] = { + type: 'GraphQLEnumType', + node, + tsType: this.convertName(node.name), + }; + return; + } + + if (node instanceof GraphQLScalarType) { + usedInputTypes[node.name] = { + type: 'GraphQLScalarType', + node, + tsType: (SCALARS[node.name] || this.config.scalars?.[node.name]?.input.type) ?? 'any', + }; + return; + } + + // GraphQLInputObjectType + usedInputTypes[node.name] = { + type: 'GraphQLInputObjectType', + node, + tsType: this.convertName(node.name), + }; + + const fields = node.getFields(); for (const field of Object.values(fields)) { const fieldType = getNamedType(field.type); - if ( - fieldType && - (fieldType instanceof GraphQLEnumType || - fieldType instanceof GraphQLInputObjectType || - fieldType instanceof GraphQLScalarType) && - !usedInputTypes[fieldType.name] - ) { - usedInputTypes[fieldType.name] = fieldType; - if (fieldType instanceof GraphQLInputObjectType) { - this.collectInnerTypesRecursively(fieldType, usedInputTypes); - } - } + this.collectInnerTypesRecursively(fieldType, usedInputTypes); } } @@ -400,13 +498,9 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< (foundInputType instanceof GraphQLInputObjectType || foundInputType instanceof GraphQLScalarType || foundInputType instanceof GraphQLEnumType) && - !usedInputTypes[namedTypeNode.name.value] && !isNativeNamedType(foundInputType) ) { - usedInputTypes[namedTypeNode.name.value] = foundInputType; - if (foundInputType instanceof GraphQLInputObjectType) { - this.collectInnerTypesRecursively(foundInputType, usedInputTypes); - } + this.collectInnerTypesRecursively(foundInputType, usedInputTypes); } }, }); @@ -427,7 +521,11 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< const namedType = getNamedType(fieldType); if (namedType instanceof GraphQLEnumType) { - usedInputTypes[namedType.name] = namedType; + usedInputTypes[namedType.name] = { + type: 'GraphQLEnumType', + node: namedType, + tsType: this.convertName(namedType.name), + }; } } }, From 58a9ac9a82395a9c67e7a1ab1a82259e5d88452b Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Sun, 21 Dec 2025 21:05:19 +1100 Subject: [PATCH 17/20] Revert Client Preset changes --- .../gql/fragment-masking.ts | 73 +++++++++++-------- .../gql/fragment-masking.ts | 73 +++++++++++-------- .../gql/fragment-masking.ts | 73 +++++++++++-------- .../graphql/fragment-masking.ts | 73 +++++++++++-------- .../src/gql/fragment-masking.ts | 73 +++++++++++-------- .../src/gql/fragment-masking.ts | 73 +++++++++++-------- .../src/gql/fragment-masking.ts | 73 +++++++++++-------- .../apollo-client/src/gql/fragment-masking.ts | 73 +++++++++++-------- .../http-executor/src/gql/fragment-masking.ts | 73 +++++++++++-------- .../src/gql/fragment-masking.ts | 73 +++++++++++-------- .../react/urql/src/gql/fragment-masking.ts | 73 +++++++++++-------- .../src/gql/fragment-masking.ts | 73 +++++++++++-------- .../src/gql/fragment-masking.ts | 73 +++++++++++-------- .../src/gql/fragment-masking.ts | 73 +++++++++++-------- .../src/gql/fragment-masking.ts | 73 +++++++++++-------- .../vite-react-ts/src/gql/fragment-masking.ts | 73 +++++++++++-------- .../src/gql/fragment-masking.ts | 73 +++++++++++-------- examples/vue/urql/src/gql/fragment-masking.ts | 73 +++++++++++-------- .../vue/villus/src/gql/fragment-masking.ts | 73 +++++++++++-------- .../yoga-tests/src/gql/fragment-masking.ts | 73 +++++++++++-------- .../client/src/fragment-masking-plugin.ts | 72 ++++++++++-------- .../client/tests/client-preset.spec.ts | 68 +++++++++-------- 22 files changed, 961 insertions(+), 639 deletions(-) diff --git a/dev-test/gql-tag-operations-masking/gql/fragment-masking.ts b/dev-test/gql-tag-operations-masking/gql/fragment-masking.ts index 4bfad5cd554..a6b3407f590 100644 --- a/dev-test/gql-tag-operations-masking/gql/fragment-masking.ts +++ b/dev-test/gql-tag-operations-masking/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/dev-test/gql-tag-operations-urql/gql/fragment-masking.ts b/dev-test/gql-tag-operations-urql/gql/fragment-masking.ts index 4bfad5cd554..a6b3407f590 100644 --- a/dev-test/gql-tag-operations-urql/gql/fragment-masking.ts +++ b/dev-test/gql-tag-operations-urql/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/dev-test/gql-tag-operations/gql/fragment-masking.ts b/dev-test/gql-tag-operations/gql/fragment-masking.ts index 4bfad5cd554..a6b3407f590 100644 --- a/dev-test/gql-tag-operations/gql/fragment-masking.ts +++ b/dev-test/gql-tag-operations/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/dev-test/gql-tag-operations/graphql/fragment-masking.ts b/dev-test/gql-tag-operations/graphql/fragment-masking.ts index 4bfad5cd554..a6b3407f590 100644 --- a/dev-test/gql-tag-operations/graphql/fragment-masking.ts +++ b/dev-test/gql-tag-operations/graphql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/persisted-documents-string-mode/src/gql/fragment-masking.ts b/examples/persisted-documents-string-mode/src/gql/fragment-masking.ts index 22e69da9617..dedac7e7f7e 100644 --- a/examples/persisted-documents-string-mode/src/gql/fragment-masking.ts +++ b/examples/persisted-documents-string-mode/src/gql/fragment-masking.ts @@ -12,39 +12,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -65,5 +80,5 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/persisted-documents/src/gql/fragment-masking.ts b/examples/persisted-documents/src/gql/fragment-masking.ts index 0a186994ef8..c469b9c617c 100644 --- a/examples/persisted-documents/src/gql/fragment-masking.ts +++ b/examples/persisted-documents/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/react/apollo-client-defer/src/gql/fragment-masking.ts b/examples/react/apollo-client-defer/src/gql/fragment-masking.ts index 0a186994ef8..c469b9c617c 100644 --- a/examples/react/apollo-client-defer/src/gql/fragment-masking.ts +++ b/examples/react/apollo-client-defer/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/react/apollo-client/src/gql/fragment-masking.ts b/examples/react/apollo-client/src/gql/fragment-masking.ts index 0a186994ef8..c469b9c617c 100644 --- a/examples/react/apollo-client/src/gql/fragment-masking.ts +++ b/examples/react/apollo-client/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/react/http-executor/src/gql/fragment-masking.ts b/examples/react/http-executor/src/gql/fragment-masking.ts index 0a186994ef8..c469b9c617c 100644 --- a/examples/react/http-executor/src/gql/fragment-masking.ts +++ b/examples/react/http-executor/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/react/tanstack-react-query/src/gql/fragment-masking.ts b/examples/react/tanstack-react-query/src/gql/fragment-masking.ts index 22e69da9617..dedac7e7f7e 100644 --- a/examples/react/tanstack-react-query/src/gql/fragment-masking.ts +++ b/examples/react/tanstack-react-query/src/gql/fragment-masking.ts @@ -12,39 +12,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -65,5 +80,5 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/react/urql/src/gql/fragment-masking.ts b/examples/react/urql/src/gql/fragment-masking.ts index 22e69da9617..dedac7e7f7e 100644 --- a/examples/react/urql/src/gql/fragment-masking.ts +++ b/examples/react/urql/src/gql/fragment-masking.ts @@ -12,39 +12,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -65,5 +80,5 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/typescript-esm/src/gql/fragment-masking.ts b/examples/typescript-esm/src/gql/fragment-masking.ts index 4bfad5cd554..a6b3407f590 100644 --- a/examples/typescript-esm/src/gql/fragment-masking.ts +++ b/examples/typescript-esm/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/typescript-graphql-request/src/gql/fragment-masking.ts b/examples/typescript-graphql-request/src/gql/fragment-masking.ts index 22e69da9617..dedac7e7f7e 100644 --- a/examples/typescript-graphql-request/src/gql/fragment-masking.ts +++ b/examples/typescript-graphql-request/src/gql/fragment-masking.ts @@ -12,39 +12,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -65,5 +80,5 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/vite/vite-react-cts/src/gql/fragment-masking.ts b/examples/vite/vite-react-cts/src/gql/fragment-masking.ts index 0a186994ef8..c469b9c617c 100644 --- a/examples/vite/vite-react-cts/src/gql/fragment-masking.ts +++ b/examples/vite/vite-react-cts/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/vite/vite-react-mts/src/gql/fragment-masking.ts b/examples/vite/vite-react-mts/src/gql/fragment-masking.ts index 0a186994ef8..c469b9c617c 100644 --- a/examples/vite/vite-react-mts/src/gql/fragment-masking.ts +++ b/examples/vite/vite-react-mts/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/vite/vite-react-ts/src/gql/fragment-masking.ts b/examples/vite/vite-react-ts/src/gql/fragment-masking.ts index 0a186994ef8..c469b9c617c 100644 --- a/examples/vite/vite-react-ts/src/gql/fragment-masking.ts +++ b/examples/vite/vite-react-ts/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/vue/apollo-composable/src/gql/fragment-masking.ts b/examples/vue/apollo-composable/src/gql/fragment-masking.ts index 024a8ba0caa..a97fd9b635e 100644 --- a/examples/vue/apollo-composable/src/gql/fragment-masking.ts +++ b/examples/vue/apollo-composable/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/vue/urql/src/gql/fragment-masking.ts b/examples/vue/urql/src/gql/fragment-masking.ts index 024a8ba0caa..a97fd9b635e 100644 --- a/examples/vue/urql/src/gql/fragment-masking.ts +++ b/examples/vue/urql/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/vue/villus/src/gql/fragment-masking.ts b/examples/vue/villus/src/gql/fragment-masking.ts index 024a8ba0caa..a97fd9b635e 100644 --- a/examples/vue/villus/src/gql/fragment-masking.ts +++ b/examples/vue/villus/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/examples/yoga-tests/src/gql/fragment-masking.ts b/examples/yoga-tests/src/gql/fragment-masking.ts index 0a186994ef8..c469b9c617c 100644 --- a/examples/yoga-tests/src/gql/fragment-masking.ts +++ b/examples/yoga-tests/src/gql/fragment-masking.ts @@ -13,39 +13,54 @@ export type FragmentType> : never; // return non-nullable if `fragmentType` is non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType; // return nullable if `fragmentType` is undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; // return nullable if `fragmentType` is nullable or undefined -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; // return array of nullable if `fragmentType` is array of nullable -export function useFragment>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined; -export function useFragment>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray; +// return readonly array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined; +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: + | FragmentType> + | Array>> + | ReadonlyArray>> + | null + | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -69,5 +84,5 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } diff --git a/packages/presets/client/src/fragment-masking-plugin.ts b/packages/presets/client/src/fragment-masking-plugin.ts index 1784eba6493..f3e69e3c6bf 100644 --- a/packages/presets/client/src/fragment-masking-plugin.ts +++ b/packages/presets/client/src/fragment-masking-plugin.ts @@ -24,48 +24,60 @@ const defaultUnmaskFunctionName = 'useFragment'; const createUnmaskFunctionTypeDefinitions = (unmaskFunctionName = defaultUnmaskFunctionName) => [ `// return non-nullable if \`fragmentType\` is non-nullable -export function ${unmaskFunctionName}>( - _documentNode: F, - fragmentType: FragmentType -): ResultOf;`, +export function ${unmaskFunctionName}( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> +): TType;`, `// return nullable if \`fragmentType\` is undefined -export function ${unmaskFunctionName}>( - _documentNode: F, - fragmentType: FragmentType | undefined -): ResultOf | undefined;`, +export function ${unmaskFunctionName}( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined;`, `// return nullable if \`fragmentType\` is nullable -export function ${unmaskFunctionName}>( - _documentNode: F, - fragmentType: FragmentType | null -): ResultOf | null;`, +export function ${unmaskFunctionName}( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null;`, `// return nullable if \`fragmentType\` is nullable or undefined -export function ${unmaskFunctionName}>( - _documentNode: F, - fragmentType: FragmentType | null | undefined -): ResultOf | null | undefined;`, +export function ${unmaskFunctionName}( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined;`, `// return array of non-nullable if \`fragmentType\` is array of non-nullable -export function ${unmaskFunctionName}>( - _documentNode: F, - fragmentType: ReadonlyArray> -): ReadonlyArray>;`, +export function ${unmaskFunctionName}( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array;`, `// return array of nullable if \`fragmentType\` is array of nullable -export function ${unmaskFunctionName}>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined -): ReadonlyArray> | null | undefined;`, +export function ${unmaskFunctionName}( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined;`, + + `// return readonly array of non-nullable if \`fragmentType\` is array of non-nullable +export function ${unmaskFunctionName}( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> +): ReadonlyArray;`, + + `// return readonly array of nullable if \`fragmentType\` is array of nullable +export function ${unmaskFunctionName}( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined +): ReadonlyArray | null | undefined;`, ]; const createUnmaskFunction = (unmaskFunctionName = defaultUnmaskFunctionName) => ` ${createUnmaskFunctionTypeDefinitions(unmaskFunctionName).join('\n')} -export function ${unmaskFunctionName}>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined -): ResultOf | ReadonlyArray> | null | undefined { +export function ${unmaskFunctionName}( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | Array>> | ReadonlyArray>> | null | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } `; @@ -84,7 +96,7 @@ export function isFragmentReady( if (!deferredFields || !fragName) return true; const fields = deferredFields[fragName] ?? []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } `; } @@ -103,7 +115,7 @@ export function isFragmentReady( const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } `; }; diff --git a/packages/presets/client/tests/client-preset.spec.ts b/packages/presets/client/tests/client-preset.spec.ts index c8156146bf5..403e58e3e4f 100644 --- a/packages/presets/client/tests/client-preset.spec.ts +++ b/packages/presets/client/tests/client-preset.spec.ts @@ -813,39 +813,49 @@ export * from "./gql";`); : never; // return non-nullable if \`fragmentType\` is non-nullable - export function iLikeTurtles>( - _documentNode: F, - fragmentType: FragmentType - ): ResultOf; + export function iLikeTurtles( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> + ): TType; // return nullable if \`fragmentType\` is undefined - export function iLikeTurtles>( - _documentNode: F, - fragmentType: FragmentType | undefined - ): ResultOf | undefined; + export function iLikeTurtles( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined + ): TType | undefined; // return nullable if \`fragmentType\` is nullable - export function iLikeTurtles>( - _documentNode: F, - fragmentType: FragmentType | null - ): ResultOf | null; + export function iLikeTurtles( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null + ): TType | null; // return nullable if \`fragmentType\` is nullable or undefined - export function iLikeTurtles>( - _documentNode: F, - fragmentType: FragmentType | null | undefined - ): ResultOf | null | undefined; + export function iLikeTurtles( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined + ): TType | null | undefined; // return array of non-nullable if \`fragmentType\` is array of non-nullable - export function iLikeTurtles>( - _documentNode: F, - fragmentType: ReadonlyArray> - ): ReadonlyArray>; + export function iLikeTurtles( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> + ): Array; // return array of nullable if \`fragmentType\` is array of nullable - export function iLikeTurtles>( - _documentNode: F, - fragmentType: ReadonlyArray> | null | undefined - ): ReadonlyArray> | null | undefined; - export function iLikeTurtles>( - _documentNode: F, - fragmentType: FragmentType | ReadonlyArray> | null | undefined - ): ResultOf | ReadonlyArray> | null | undefined { + export function iLikeTurtles( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined + ): Array | null | undefined; + // return readonly array of non-nullable if \`fragmentType\` is array of non-nullable + export function iLikeTurtles( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> + ): ReadonlyArray; + // return readonly array of nullable if \`fragmentType\` is array of nullable + export function iLikeTurtles( + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined + ): ReadonlyArray | null | undefined; + export function iLikeTurtles( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | Array>> | ReadonlyArray>> | null | undefined + ): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } @@ -870,7 +880,7 @@ export * from "./gql";`); const fragName = fragDef?.name?.value; const fields = (fragName && deferredFields[fragName]) || []; - return fields.length > 0 && fields.every((field: keyof TFrag) => data && field in data); + return fields.length > 0 && fields.every(field => data && field in data); } " `); From 26b09e6fb42d616a779a4f40bc18b4f84331f1b8 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Sun, 21 Dec 2025 22:58:59 +1100 Subject: [PATCH 18/20] Add oneOf directive for GraphQL 15 --- .../operations/tests/ts-documents.standalone.input.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/plugins/typescript/operations/tests/ts-documents.standalone.input.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.standalone.input.spec.ts index c10cfe18f9c..2071c3adf0e 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.input.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.input.spec.ts @@ -253,6 +253,8 @@ describe('TypeScript Operations Plugin - Input', () => { it('generates @oneOf input correctly', async () => { const schema = buildSchema(/* GraphQL */ ` + directive @oneOf on INPUT_OBJECT + type Query { users(input: UsersInput!): [User!]! } From 0adcbed3cedc3bfabbde5c9ff566aaab4b98397a Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Mon, 22 Dec 2025 23:01:36 +1100 Subject: [PATCH 19/20] Update changeset --- .changeset/every-queens-sin.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/every-queens-sin.md diff --git a/.changeset/every-queens-sin.md b/.changeset/every-queens-sin.md new file mode 100644 index 00000000000..cda139690b5 --- /dev/null +++ b/.changeset/every-queens-sin.md @@ -0,0 +1,6 @@ +--- +'@graphql-codegen/typescript-operations': major +'@graphql-codegen/client-preset': major +--- + +Conditionally generate input types and output enums into target file From 9cea149eca3ab6523fe52a634e7ae9f6bac51c3c Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Wed, 24 Dec 2025 20:18:27 +1100 Subject: [PATCH 20/20] Refactor IIFE --- .../typescript/operations/src/visitor.ts | 128 ++++++++++-------- 1 file changed, 69 insertions(+), 59 deletions(-) diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 0c23975b24d..81d92421a46 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -242,23 +242,11 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< _path?: Array, ancestors?: Array ): string { - const oneOfDetails = (function parseOneOf( - schema: GraphQLSchema - ): { isOneOfInputValue: true; realParentDef: TypeDefinitionNode } | { isOneOfInputValue: false } { - const realParentDef = ancestors?.[ancestors.length - 1]; - if (realParentDef) { - const parentType = schema.getType(realParentDef.name.value); - if (isOneOfInputObjectType(parentType)) { - if (node.type.kind === Kind.NON_NULL_TYPE) { - throw new Error( - 'Fields on an input object type can not be non-nullable. It seems like the schema was not validated.' - ); - } - return { isOneOfInputValue: true, realParentDef }; - } - } - return { isOneOfInputValue: false }; - })(this._schema); + const oneOfDetails = parseOneOfInputValue({ + node, + schema: this._schema, + ancestors, + }); // 1. Flatten GraphQL type nodes to make it easier to turn into string // GraphQL type nodes may have `NonNullType` type before each `ListType` or `NamedType` @@ -270,34 +258,11 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< // - [Thing!] // - [Thing]! // - [Thing!]! - const typeNodes: Array< - { type: 'ListType'; isNonNullable: boolean } | { type: 'NamedType'; isNonNullable: boolean; name: string } - > = []; - (function collectAndFlattenTypeNodes({ - currentTypeNode, - isPreviousNodeNonNullable, - }: { - currentTypeNode: TypeNode; - isPreviousNodeNonNullable: boolean; - }): void { - if (currentTypeNode.kind === Kind.NON_NULL_TYPE) { - const nextTypeNode = currentTypeNode.type; - collectAndFlattenTypeNodes({ currentTypeNode: nextTypeNode, isPreviousNodeNonNullable: true }); - } else if (currentTypeNode.kind === Kind.LIST_TYPE) { - typeNodes.push({ type: 'ListType', isNonNullable: isPreviousNodeNonNullable }); - - const nextTypeNode = currentTypeNode.type; - collectAndFlattenTypeNodes({ currentTypeNode: nextTypeNode, isPreviousNodeNonNullable: false }); - } else if (currentTypeNode.kind === Kind.NAMED_TYPE) { - typeNodes.push({ - type: 'NamedType', - isNonNullable: isPreviousNodeNonNullable, - name: currentTypeNode.name.value, - }); - } - })({ + const typeNodes: Parameters[0]['typeNodes'] = []; + collectAndFlattenTypeNodes({ currentTypeNode: node.type, isPreviousNodeNonNullable: oneOfDetails.isOneOfInputValue, // If the InputValue is part of @oneOf input, we treat it as non-null (even if it must be null in the schema) + typeNodes, }); // 2. Generate the type of a TypeScript field declaration @@ -369,25 +334,16 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< // | { byId?: never; byEmail: string; byLegacyId?: never } // | { byId?: never; byEmail?: never; byLegacyId: string | number } // ``` - if (oneOfDetails.isOneOfInputValue) { - const parentType = this._schema.getType(oneOfDetails.realParentDef.name.value); - if (isOneOfInputObjectType(parentType)) { - if (node.type.kind === Kind.NON_NULL_TYPE) { - throw new Error( - 'Fields on an input object type can not be non-nullable. It seems like the schema was not validated.' - ); - } - const fieldParts: Array = []; - for (const fieldName of Object.keys(parentType.getFields())) { - if (fieldName === node.name.value) { - fieldParts.push(currentInputValue); - continue; - } - fieldParts.push(`${readonlyPart}${fieldName}?: never;`); + const fieldParts: Array = []; + for (const fieldName of Object.keys(oneOfDetails.parentType.getFields())) { + if (fieldName === node.name.value) { + fieldParts.push(currentInputValue); + continue; } - return indent(`{ ${fieldParts.join(' ')} }`); + fieldParts.push(`${readonlyPart}${fieldName}?: never;`); } + return indent(`{ ${fieldParts.join(' ')} }`); } // If field is not part of @oneOf input type, then it's a input value, just return as-is @@ -568,3 +524,57 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< return "export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };"; } } + +function parseOneOfInputValue({ + node, + schema, + ancestors, +}: { + node: InputValueDefinitionNode; + schema: GraphQLSchema; + ancestors?: Array; +}): + | { isOneOfInputValue: true; realParentDef: TypeDefinitionNode; parentType: GraphQLInputObjectType } + | { isOneOfInputValue: false } { + const realParentDef = ancestors?.[ancestors.length - 1]; + if (realParentDef) { + const parentType = schema.getType(realParentDef.name.value); + if (isOneOfInputObjectType(parentType)) { + if (node.type.kind === Kind.NON_NULL_TYPE) { + throw new Error( + 'Fields on an input object type can not be non-nullable. It seems like the schema was not validated.' + ); + } + return { isOneOfInputValue: true, realParentDef, parentType }; + } + } + return { isOneOfInputValue: false }; +} + +function collectAndFlattenTypeNodes({ + currentTypeNode, + isPreviousNodeNonNullable, + typeNodes, +}: { + currentTypeNode: TypeNode; + isPreviousNodeNonNullable: boolean; + typeNodes: Array< + { type: 'ListType'; isNonNullable: boolean } | { type: 'NamedType'; isNonNullable: boolean; name: string } + >; +}): void { + if (currentTypeNode.kind === Kind.NON_NULL_TYPE) { + const nextTypeNode = currentTypeNode.type; + collectAndFlattenTypeNodes({ currentTypeNode: nextTypeNode, isPreviousNodeNonNullable: true, typeNodes }); + } else if (currentTypeNode.kind === Kind.LIST_TYPE) { + typeNodes.push({ type: 'ListType', isNonNullable: isPreviousNodeNonNullable }); + + const nextTypeNode = currentTypeNode.type; + collectAndFlattenTypeNodes({ currentTypeNode: nextTypeNode, isPreviousNodeNonNullable: false, typeNodes }); + } else if (currentTypeNode.kind === Kind.NAMED_TYPE) { + typeNodes.push({ + type: 'NamedType', + isNonNullable: isPreviousNodeNonNullable, + name: currentTypeNode.name.value, + }); + } +}