From 204af7105d38cb7341c7812c50b4479824b4b6f9 Mon Sep 17 00:00:00 2001 From: Maarten Staa Date: Fri, 12 Jun 2020 14:14:06 +0200 Subject: [PATCH 1/5] Use union types for __typename selections where possible. When using fragments, the __typename is now correctly set to a union of the remaining types. In the case where the type name is selected without the use of fragments, the value is a union of all possible types. --- src/TypeScriptGenerator.ts | 83 ++++++++++++++----- .../TypeScriptGenerator-test.ts.snap | 12 +-- 2 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/TypeScriptGenerator.ts b/src/TypeScriptGenerator.ts index 43fd3707..62304493 100644 --- a/src/TypeScriptGenerator.ts +++ b/src/TypeScriptGenerator.ts @@ -9,6 +9,7 @@ import { TypeGenerator, TypeID } from "relay-compiler"; +import { UnionTypeID } from "relay-compiler/lib/core/Schema"; import { TypeGeneratorOptions } from "relay-compiler/lib/language/RelayLanguagePluginInterface"; import * as FlattenTransform from "relay-compiler/lib/transforms/FlattenTransform"; import * as MaskTransform from "relay-compiler/lib/transforms/MaskTransform"; @@ -127,7 +128,8 @@ function makeProp( selection: Selection, state: State, unmasked: boolean, - concreteType?: string + concreteType?: string, + unionType?: UnionTypeID ): ts.PropertySignature { let { value } = selection; @@ -135,6 +137,12 @@ function makeProp( if (schemaName === "__typename" && concreteType) { value = ts.createLiteralTypeNode(ts.createLiteral(concreteType)); + } else if (schemaName === "__typename" && unionType) { + value = ts.createUnionTypeNode( + schema + .getUnionTypes(unionType) + .map(type => ts.createLiteralTypeNode(ts.createLiteral(type.name))) + ); } else if (nodeType) { value = transformScalarType( schema, @@ -142,6 +150,7 @@ function makeProp( state, selectionsToAST( schema, + nodeType, [Array.from(nullthrows(nodeSelections).values())], state, unmasked @@ -167,6 +176,7 @@ const onlySelectsTypename = (selections: Selection[]) => function selectionsToAST( schema: Schema, + nodeType: TypeID | null, selections: ReadonlyArray>, state: State, unmasked: boolean, @@ -218,27 +228,49 @@ function selectionsToAST( ); } - // It might be some other type then the listed concrete types. Ideally, we - // would set the type to diff(string, set of listed concrete types), but - // this doesn't exist in Flow at the time. - types.push( - Array.from(typenameAliases).map(typenameAlias => { - const otherProp = objectTypeProperty( - typenameAlias, - ts.createLiteralTypeNode(ts.createLiteral("%other")) - ); + // It might be some other type then the listed concrete types. We try to + // figure out which types remain here. + let possibleTypesLeft: TypeID[] | null = null; + const innerType = nodeType !== null ? schema.getRawType(nodeType) : null; + if (innerType !== null && schema.isUnion(innerType)) { + const typesSeen = Object.keys(byConcreteType); + possibleTypesLeft = schema + .getUnionTypes(innerType) + .filter(type => !typesSeen.includes(type.name)); + } - const otherPropWithComment = ts.addSyntheticLeadingComment( - otherProp, - ts.SyntaxKind.MultiLineCommentTrivia, - "This will never be '%other', but we need some\n" + - "value in case none of the concrete values match.", - true - ); + // If we don't know which types are left we set the value to "%other", + // otherwise return a union of type names. + if (!possibleTypesLeft || possibleTypesLeft.length > 0) { + types.push( + Array.from(typenameAliases).map(typenameAlias => { + const otherProp = objectTypeProperty( + typenameAlias, + possibleTypesLeft + ? ts.createUnionTypeNode( + possibleTypesLeft.map(type => + ts.createLiteralTypeNode(ts.createLiteral(type.name)) + ) + ) + : ts.createLiteralTypeNode(ts.createLiteral("%other")) + ); - return otherPropWithComment; - }) - ); + if (possibleTypesLeft) { + return otherProp; + } + + const otherPropWithComment = ts.addSyntheticLeadingComment( + otherProp, + ts.SyntaxKind.MultiLineCommentTrivia, + "This will never be '%other', but we need some\n" + + "value in case none of the concrete values match.", + true + ); + + return otherPropWithComment; + }) + ); + } } else { let selectionMap = selectionsToMap(Array.from(baseFields.values())); @@ -256,7 +288,9 @@ function selectionsToAST( const selectionMapValues = groupRefs(Array.from(selectionMap.values())).map( sel => - isTypenameSelection(sel) && sel.concreteType + isTypenameSelection(sel) && + (sel.concreteType || + (nodeType && schema.isUnion(schema.getNullableType(nodeType)))) ? makeProp( schema, { @@ -265,7 +299,10 @@ function selectionsToAST( }, state, unmasked, - sel.concreteType + sel.concreteType, + nodeType && schema.isUnion(schema.getNullableType(nodeType)) + ? schema.getNullableType(nodeType) + : undefined ) : makeProp(schema, sel, state, unmasked) ); @@ -438,6 +475,7 @@ function createVisitor( `${node.name}Response`, selectionsToAST( schema, + null, /* $FlowFixMe: selections have already been transformed */ (node.selections as any) as ReadonlyArray>, state, @@ -556,6 +594,7 @@ function createVisitor( const unmasked = node.metadata != null && node.metadata.mask === false; const baseType = selectionsToAST( schema, + node.type, selections, state, unmasked, diff --git a/test/__snapshots__/TypeScriptGenerator-test.ts.snap b/test/__snapshots__/TypeScriptGenerator-test.ts.snap index fe0c155e..ecb8f8e9 100644 --- a/test/__snapshots__/TypeScriptGenerator-test.ts.snap +++ b/test/__snapshots__/TypeScriptGenerator-test.ts.snap @@ -446,9 +446,7 @@ export type UnionTypeTestResponse = { readonly __typename: "FakeNode"; readonly id: string; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "NonNode"; }) | null; }; export type UnionTypeTest = { @@ -1684,7 +1682,7 @@ export type RelayClientIDFieldQueryResponse = { readonly id: string; readonly commentBody?: { readonly __id: string; - readonly __typename: string; + readonly __typename: "PlainCommentBody" | "MarkdownCommentBody"; readonly text?: { readonly __id: string; readonly __typename: string; @@ -2680,9 +2678,7 @@ export type UnionTypeTestResponse = { readonly __typename: "FakeNode"; readonly id: string; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "NonNode"; }) | null; }; export type UnionTypeTest = { @@ -3918,7 +3914,7 @@ export type RelayClientIDFieldQueryResponse = { readonly id: string; readonly commentBody?: { readonly __id: string; - readonly __typename: string; + readonly __typename: "PlainCommentBody" | "MarkdownCommentBody"; readonly text?: { readonly __id: string; readonly __typename: string; From 21e282030f4dd95dfbb16a29309fa8b75725734b Mon Sep 17 00:00:00 2001 From: Maarten Staa Date: Fri, 12 Jun 2020 14:15:30 +0200 Subject: [PATCH 2/5] Fix duplicated fields in generated types. When selecting a __typename both on the root of the union and in a fragment, the __typename was duplicated in the output, this commit fixes that by only including the base fields that are not also selected in the concrete types. --- src/TypeScriptGenerator.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/TypeScriptGenerator.ts b/src/TypeScriptGenerator.ts index 62304493..e36ca0c7 100644 --- a/src/TypeScriptGenerator.ts +++ b/src/TypeScriptGenerator.ts @@ -215,10 +215,19 @@ function selectionsToAST( const typenameAliases = new Set(); for (const concreteType in byConcreteType) { + const concreteTypeSelections = byConcreteType[concreteType]; + const concreteTypeSelectionsNames = concreteTypeSelections.map( + selection => selection.schemaName + ); + types.push( groupRefs([ - ...Array.from(baseFields.values()), - ...byConcreteType[concreteType] + // Deduplicate any fields also selected on the concrete type. + ...Array.from(baseFields.values()).filter( + selection => + !concreteTypeSelectionsNames.includes(selection.schemaName) + ), + ...concreteTypeSelections ]).map(selection => { if (selection.schemaName === "__typename") { typenameAliases.add(selection.key); From c8ae7f4a155cade97415cf736caef63c72571131 Mon Sep 17 00:00:00 2001 From: Maarten Staa Date: Fri, 12 Jun 2020 16:30:52 +0200 Subject: [PATCH 3/5] Add better handling for fragment usage. This makes the type generation play better with the checks for "is the __typename the only field being selected from the base type". For example: fragment Foo_bar on Baz { __typename ...OtherObject_property ... on B { __typename value } } Previously, this would result in something like { __typename: "A" | "B"; value?: string; " $fragmentRefs": "..." } This breaks type guards in Typescript: if (bar.__typename == "B") { // Uh-oh, value is still possibly undefined } With this change, since the required fragments are tracked separately, the resulting code looks more like this: { __typename: "B"; value: string; " $fragmentRefs": "..." } | { __typename: "A"; " $fragmentRefs": "..." } --- src/TypeScriptGenerator.ts | 71 ++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/TypeScriptGenerator.ts b/src/TypeScriptGenerator.ts index e36ca0c7..a9c78e47 100644 --- a/src/TypeScriptGenerator.ts +++ b/src/TypeScriptGenerator.ts @@ -183,6 +183,7 @@ function selectionsToAST( fragmentTypeName?: string ) { const baseFields = new Map(); + const baseFragments = new Map(); const byConcreteType: { [type: string]: Selection[] } = {}; @@ -192,6 +193,8 @@ function selectionsToAST( if (concreteType) { byConcreteType[concreteType] = byConcreteType[concreteType] || []; byConcreteType[concreteType].push(selection); + } else if (selection.ref) { + baseFragments.set(selection.ref, selection); } else { const previousSel = baseFields.get(selection.key); @@ -219,6 +222,9 @@ function selectionsToAST( const concreteTypeSelectionsNames = concreteTypeSelections.map( selection => selection.schemaName ); + const concreteFragmentsNames = concreteTypeSelections.map( + selection => selection.ref + ); types.push( groupRefs([ @@ -227,6 +233,9 @@ function selectionsToAST( selection => !concreteTypeSelectionsNames.includes(selection.schemaName) ), + ...Array.from(baseFragments.values()).filter( + selection => !concreteFragmentsNames.includes(selection.ref) + ), ...concreteTypeSelections ]).map(selection => { if (selection.schemaName === "__typename") { @@ -251,8 +260,8 @@ function selectionsToAST( // If we don't know which types are left we set the value to "%other", // otherwise return a union of type names. if (!possibleTypesLeft || possibleTypesLeft.length > 0) { - types.push( - Array.from(typenameAliases).map(typenameAlias => { + types.push([ + ...Array.from(typenameAliases).map(typenameAlias => { const otherProp = objectTypeProperty( typenameAlias, possibleTypesLeft @@ -277,8 +286,22 @@ function selectionsToAST( ); return otherPropWithComment; - }) - ); + }), + ...(baseFragments.size > 0 + ? objectTypeProperty( + FRAGMENT_REFS, + ts.createTypeReferenceNode(FRAGMENT_REFS_TYPE_NAME, [ + ts.createUnionTypeNode( + Array.from(baseFragments.values()).map(selection => + ts.createLiteralTypeNode( + ts.createStringLiteral(selection.ref!) + ) + ) + ) + ]) + ) + : []) + ]); } } else { let selectionMap = selectionsToMap(Array.from(baseFields.values())); @@ -295,25 +318,27 @@ function selectionsToAST( ); } - const selectionMapValues = groupRefs(Array.from(selectionMap.values())).map( - sel => - isTypenameSelection(sel) && - (sel.concreteType || - (nodeType && schema.isUnion(schema.getNullableType(nodeType)))) - ? makeProp( - schema, - { - ...sel, - conditional: false - }, - state, - unmasked, - sel.concreteType, - nodeType && schema.isUnion(schema.getNullableType(nodeType)) - ? schema.getNullableType(nodeType) - : undefined - ) - : makeProp(schema, sel, state, unmasked) + const selectionMapValues = groupRefs([ + ...Array.from(baseFragments.values()), + ...Array.from(selectionMap.values()) + ]).map(sel => + isTypenameSelection(sel) && + (sel.concreteType || + (nodeType && schema.isUnion(schema.getNullableType(nodeType)))) + ? makeProp( + schema, + { + ...sel, + conditional: false + }, + state, + unmasked, + sel.concreteType, + nodeType && schema.isUnion(schema.getNullableType(nodeType)) + ? schema.getNullableType(nodeType) + : undefined + ) + : makeProp(schema, sel, state, unmasked) ); types.push(selectionMapValues); From f28b39d25b88c70011a555222ef5273aece5c136 Mon Sep 17 00:00:00 2001 From: Maarten Staa Date: Tue, 16 Jun 2020 17:59:09 +0200 Subject: [PATCH 4/5] Support typename union for interfaces as well as unions. --- src/TypeScriptGenerator.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/TypeScriptGenerator.ts b/src/TypeScriptGenerator.ts index a9c78e47..532b4315 100644 --- a/src/TypeScriptGenerator.ts +++ b/src/TypeScriptGenerator.ts @@ -249,12 +249,16 @@ function selectionsToAST( // It might be some other type then the listed concrete types. We try to // figure out which types remain here. let possibleTypesLeft: TypeID[] | null = null; - const innerType = nodeType !== null ? schema.getRawType(nodeType) : null; - if (innerType !== null && schema.isUnion(innerType)) { + const innerType = + nodeType !== null ? schema.getNullableType(nodeType) : null; + if ( + innerType !== null && + (schema.isUnion(innerType) || schema.isInterface(innerType)) + ) { const typesSeen = Object.keys(byConcreteType); - possibleTypesLeft = schema - .getUnionTypes(innerType) - .filter(type => !typesSeen.includes(type.name)); + possibleTypesLeft = Array.from(schema.getPossibleTypes(innerType)).filter( + type => !typesSeen.includes(type.name) + ); } // If we don't know which types are left we set the value to "%other", From dc93fb871602b69daf715a9c2d3b1602f8c06844 Mon Sep 17 00:00:00 2001 From: Maarten Staa Date: Tue, 16 Jun 2020 18:07:28 +0200 Subject: [PATCH 5/5] Add better handling of unions when also selecting base fields other than the type name, and deduplicate output. The resulting type is now a union of possible types filtered by the type name, combined with all the base fields as an intersection. --- src/TypeScriptGenerator.ts | 149 +++++----- .../TypeScriptGenerator-test.ts.snap | 254 +++++++----------- 2 files changed, 180 insertions(+), 223 deletions(-) diff --git a/src/TypeScriptGenerator.ts b/src/TypeScriptGenerator.ts index 532b4315..9c03aa4f 100644 --- a/src/TypeScriptGenerator.ts +++ b/src/TypeScriptGenerator.ts @@ -168,11 +168,13 @@ function makeProp( const isTypenameSelection = (selection: Selection) => selection.schemaName === "__typename"; +const isFragmentSelection = (selection: Selection) => !!selection.ref; + const hasTypenameSelection = (selections: Selection[]) => selections.some(isTypenameSelection); -const onlySelectsTypename = (selections: Selection[]) => - selections.every(isTypenameSelection); +const onlySelectsFragments = (selections: Selection[]) => + selections.every(isFragmentSelection); function selectionsToAST( schema: Schema, @@ -188,7 +190,13 @@ function selectionsToAST( const byConcreteType: { [type: string]: Selection[] } = {}; flattenArray(selections).forEach(selection => { - const { concreteType } = selection; + let { concreteType } = selection; + + // If the concrete type matches the node type, we can add this to the base fields + // and fragments instead. + if (nodeType && concreteType && nodeType.name === concreteType) { + concreteType = undefined; + } if (concreteType) { byConcreteType[concreteType] = byConcreteType[concreteType] || []; @@ -205,16 +213,28 @@ function selectionsToAST( } }); - const types: ts.PropertySignature[][] = []; + // If there are any concrete types that only select fragments, move those + // fragments to the base fragments instead. + for (const concreteType in byConcreteType) { + const concreteTypeSelections = byConcreteType[concreteType]; + if (onlySelectsFragments(concreteTypeSelections)) { + concreteTypeSelections.forEach(selection => + baseFragments.set(selection.ref!, selection) + ); + + delete byConcreteType[concreteType]; + } + } - if ( + const concreteTypes: ts.PropertySignature[][] = []; + const typeFieldsPresentForUnion = Object.keys(byConcreteType).length > 0 && - onlySelectsTypename(Array.from(baseFields.values())) && (hasTypenameSelection(Array.from(baseFields.values())) || Object.keys(byConcreteType).every(type => hasTypenameSelection(byConcreteType[type]) - )) - ) { + )); + + if (typeFieldsPresentForUnion) { const typenameAliases = new Set(); for (const concreteType in byConcreteType) { @@ -222,20 +242,15 @@ function selectionsToAST( const concreteTypeSelectionsNames = concreteTypeSelections.map( selection => selection.schemaName ); - const concreteFragmentsNames = concreteTypeSelections.map( - selection => selection.ref - ); - types.push( + concreteTypes.push( groupRefs([ // Deduplicate any fields also selected on the concrete type. ...Array.from(baseFields.values()).filter( selection => + isTypenameSelection(selection) && !concreteTypeSelectionsNames.includes(selection.schemaName) ), - ...Array.from(baseFragments.values()).filter( - selection => !concreteFragmentsNames.includes(selection.ref) - ), ...concreteTypeSelections ]).map(selection => { if (selection.schemaName === "__typename") { @@ -264,8 +279,8 @@ function selectionsToAST( // If we don't know which types are left we set the value to "%other", // otherwise return a union of type names. if (!possibleTypesLeft || possibleTypesLeft.length > 0) { - types.push([ - ...Array.from(typenameAliases).map(typenameAlias => { + concreteTypes.push( + Array.from(typenameAliases).map(typenameAlias => { const otherProp = objectTypeProperty( typenameAlias, possibleTypesLeft @@ -290,26 +305,13 @@ function selectionsToAST( ); return otherPropWithComment; - }), - ...(baseFragments.size > 0 - ? objectTypeProperty( - FRAGMENT_REFS, - ts.createTypeReferenceNode(FRAGMENT_REFS_TYPE_NAME, [ - ts.createUnionTypeNode( - Array.from(baseFragments.values()).map(selection => - ts.createLiteralTypeNode( - ts.createStringLiteral(selection.ref!) - ) - ) - ) - ]) - ) - : []) - ]); + }) + ); } - } else { - let selectionMap = selectionsToMap(Array.from(baseFields.values())); + } + let selectionMap = selectionsToMap(Array.from(baseFields.values())); + if (!typeFieldsPresentForUnion) { for (const concreteType in byConcreteType) { selectionMap = mergeSelections( selectionMap, @@ -321,53 +323,58 @@ function selectionsToAST( ) ); } + } - const selectionMapValues = groupRefs([ + const baseTypeProps = groupRefs( + [ ...Array.from(baseFragments.values()), ...Array.from(selectionMap.values()) - ]).map(sel => - isTypenameSelection(sel) && - (sel.concreteType || - (nodeType && schema.isUnion(schema.getNullableType(nodeType)))) - ? makeProp( - schema, - { - ...sel, - conditional: false - }, - state, - unmasked, - sel.concreteType, - nodeType && schema.isUnion(schema.getNullableType(nodeType)) - ? schema.getNullableType(nodeType) - : undefined - ) - : makeProp(schema, sel, state, unmasked) - ); + ].filter( + selection => !typeFieldsPresentForUnion || !isTypenameSelection(selection) + ) + ).map(sel => + isTypenameSelection(sel) && + (sel.concreteType || + (nodeType && schema.isUnion(schema.getNullableType(nodeType)))) + ? makeProp( + schema, + { + ...sel, + conditional: false + }, + state, + unmasked, + sel.concreteType, + nodeType && schema.isUnion(schema.getNullableType(nodeType)) + ? schema.getNullableType(nodeType) + : undefined + ) + : makeProp(schema, sel, state, unmasked) + ); - types.push(selectionMapValues); + if (fragmentTypeName) { + baseTypeProps.push( + objectTypeProperty( + REF_TYPE, + ts.createLiteralTypeNode(ts.createStringLiteral(fragmentTypeName)) + ) + ); } - const typeElements = types.map(props => { - if (fragmentTypeName) { - props.push( - objectTypeProperty( - REF_TYPE, - ts.createLiteralTypeNode(ts.createStringLiteral(fragmentTypeName)) - ) - ); - } - - return unmasked + const propsToObject = (props: ts.PropertySignature[]) => + unmasked ? ts.createTypeLiteralNode(props) : exactObjectTypeAnnotation(props); - }); - if (typeElements.length === 1) { - return typeElements[0]; + const baseType = propsToObject(baseTypeProps); + if (concreteTypes.length === 0) { + return baseType; } - return ts.createUnionTypeNode(typeElements); + const unionType = ts.createUnionTypeNode(concreteTypes.map(propsToObject)); + return baseTypeProps.length > 0 + ? ts.createIntersectionTypeNode([unionType, baseType]) + : unionType; } // We don't have exact object types in typescript. diff --git a/test/__snapshots__/TypeScriptGenerator-test.ts.snap b/test/__snapshots__/TypeScriptGenerator-test.ts.snap index ecb8f8e9..e7ff094c 100644 --- a/test/__snapshots__/TypeScriptGenerator-test.ts.snap +++ b/test/__snapshots__/TypeScriptGenerator-test.ts.snap @@ -123,9 +123,7 @@ export type ConcreateTypes = { readonly __typename: "User"; readonly name: string | null; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "ExtraUser"; }) | null; readonly " $refType": "ConcreateTypes"; }; @@ -141,11 +139,6 @@ import { FragmentRefs } from "relay-runtime"; export type PictureFragment = { readonly __typename: "Image"; readonly " $refType": "PictureFragment"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "PictureFragment"; }; export type PictureFragment$data = PictureFragment; export type PictureFragment$key = { @@ -172,11 +165,6 @@ import { FragmentRefs } from "relay-runtime"; export type PageFragment = { readonly __typename: "Page"; readonly " $refType": "PageFragment"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "PageFragment"; }; export type PageFragment$data = PageFragment; export type PageFragment$key = { @@ -190,11 +178,6 @@ import { FragmentRefs } from "relay-runtime"; export type UserFrag1 = { readonly __typename: "User"; readonly " $refType": "UserFrag1"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "UserFrag1"; }; export type UserFrag1$data = UserFrag1; export type UserFrag1$key = { @@ -208,11 +191,6 @@ import { FragmentRefs } from "relay-runtime"; export type UserFrag2 = { readonly __typename: "User"; readonly " $refType": "UserFrag2"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "UserFrag2"; }; export type UserFrag2$data = UserFrag2; export type UserFrag2$key = { @@ -368,11 +346,6 @@ import { FragmentRefs } from "relay-runtime"; export type SomeFragment = { readonly __typename: "User"; readonly " $refType": "SomeFragment"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "SomeFragment"; }; export type SomeFragment$data = SomeFragment; export type SomeFragment$key = { @@ -1676,20 +1649,27 @@ export type RelayClientIDFieldQueryResponse = { readonly __typename: string; readonly id: string; } | null; - readonly node: { - readonly __id: string; - readonly __typename: string; - readonly id: string; - readonly commentBody?: { + readonly node: (({ + readonly __typename: "Comment"; + readonly commentBody: (({ + readonly __typename: "PlainCommentBody"; readonly __id: string; - readonly __typename: "PlainCommentBody" | "MarkdownCommentBody"; - readonly text?: { + readonly text: { readonly __id: string; readonly __typename: string; readonly text: string | null; } | null; - } | null; - } | null; + } | { + readonly __typename: "MarkdownCommentBody"; + }) & { + readonly __id: string; + }) | null; + } | { + readonly __typename: "Feedback" | "Page" | "PhotoStory" | "Story" | "User"; + }) & { + readonly __id: string; + readonly id: string; + }) | null; }; export type RelayClientIDFieldQuery = { readonly response: RelayClientIDFieldQueryResponse; @@ -1882,9 +1862,7 @@ export type TypenameInsideWithOverlappingFields = { readonly uri: string | null; } | null; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "ExtraUser"; }) | null; readonly " $refType": "TypenameInsideWithOverlappingFields"; }; @@ -1982,18 +1960,15 @@ fragment TypenameAliases on Actor { ~~~~~~~~~~ OUTPUT ~~~~~~~~~~ // TypenameInside.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameInside = { +export type TypenameInside = ({ readonly __typename: "User"; readonly firstName: string | null; - readonly " $refType": "TypenameInside"; } | { readonly __typename: "Page"; readonly username: string | null; - readonly " $refType": "TypenameInside"; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "ExtraUser"; +}) & { readonly " $refType": "TypenameInside"; }; export type TypenameInside$data = TypenameInside; @@ -2005,18 +1980,15 @@ export type TypenameInside$key = { // TypenameOutside.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameOutside = { +export type TypenameOutside = ({ readonly __typename: "User"; readonly firstName: string | null; - readonly " $refType": "TypenameOutside"; } | { readonly __typename: "Page"; readonly username: string | null; - readonly " $refType": "TypenameOutside"; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "ExtraUser"; +}) & { readonly " $refType": "TypenameOutside"; }; export type TypenameOutside$data = TypenameOutside; @@ -2028,15 +2000,21 @@ export type TypenameOutside$key = { // TypenameOutsideWithAbstractType.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameOutsideWithAbstractType = { - readonly __typename: string; +export type TypenameOutsideWithAbstractType = ({ + readonly __typename: "User"; + readonly firstName: string | null; + readonly address: { + readonly street: string | null; + readonly city: string | null; + } | null; +} | { + readonly __typename: "Comment" | "Feedback" | "Page" | "PhotoStory" | "Story"; +}) & { readonly username?: string | null; readonly address?: { readonly city: string | null; readonly country: string | null; - readonly street?: string | null; } | null; - readonly firstName?: string | null; readonly " $refType": "TypenameOutsideWithAbstractType"; }; export type TypenameOutsideWithAbstractType$data = TypenameOutsideWithAbstractType; @@ -2049,8 +2027,8 @@ export type TypenameOutsideWithAbstractType$key = { // TypenameWithoutSpreads.graphql import { FragmentRefs } from "relay-runtime"; export type TypenameWithoutSpreads = { - readonly firstName: string | null; readonly __typename: "User"; + readonly firstName: string | null; readonly " $refType": "TypenameWithoutSpreads"; }; export type TypenameWithoutSpreads$data = TypenameWithoutSpreads; @@ -2076,11 +2054,16 @@ export type TypenameWithoutSpreadsAbstractType$key = { // TypenameWithCommonSelections.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameWithCommonSelections = { - readonly __typename: string; +export type TypenameWithCommonSelections = ({ + readonly __typename: "User"; + readonly firstName: string | null; +} | { + readonly __typename: "Page"; + readonly username: string | null; +} | { + readonly __typename: "ExtraUser"; +}) & { readonly name: string | null; - readonly firstName?: string | null; - readonly username?: string | null; readonly " $refType": "TypenameWithCommonSelections"; }; export type TypenameWithCommonSelections$data = TypenameWithCommonSelections; @@ -2092,18 +2075,15 @@ export type TypenameWithCommonSelections$key = { // TypenameAlias.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameAlias = { +export type TypenameAlias = ({ readonly _typeAlias: "User"; readonly firstName: string | null; - readonly " $refType": "TypenameAlias"; } | { readonly _typeAlias: "Page"; readonly username: string | null; - readonly " $refType": "TypenameAlias"; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly _typeAlias: "%other"; + readonly _typeAlias: "ExtraUser"; +}) & { readonly " $refType": "TypenameAlias"; }; export type TypenameAlias$data = TypenameAlias; @@ -2115,23 +2095,18 @@ export type TypenameAlias$key = { // TypenameAliases.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameAliases = { +export type TypenameAliases = ({ readonly _typeAlias1: "User"; readonly _typeAlias2: "User"; readonly firstName: string | null; - readonly " $refType": "TypenameAliases"; } | { readonly _typeAlias1: "Page"; readonly _typeAlias2: "Page"; readonly username: string | null; - readonly " $refType": "TypenameAliases"; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly _typeAlias1: "%other"; - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly _typeAlias2: "%other"; + readonly _typeAlias1: "ExtraUser"; + readonly _typeAlias2: "ExtraUser"; +}) & { readonly " $refType": "TypenameAliases"; }; export type TypenameAliases$data = TypenameAliases; @@ -2355,9 +2330,7 @@ export type ConcreateTypes = { readonly __typename: "User"; readonly name: string | null; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "ExtraUser"; }) | null; readonly " $refType": "ConcreateTypes"; }; @@ -2373,11 +2346,6 @@ import { FragmentRefs } from "relay-runtime"; export type PictureFragment = { readonly __typename: "Image"; readonly " $refType": "PictureFragment"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "PictureFragment"; }; export type PictureFragment$data = PictureFragment; export type PictureFragment$key = { @@ -2404,11 +2372,6 @@ import { FragmentRefs } from "relay-runtime"; export type PageFragment = { readonly __typename: "Page"; readonly " $refType": "PageFragment"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "PageFragment"; }; export type PageFragment$data = PageFragment; export type PageFragment$key = { @@ -2422,11 +2385,6 @@ import { FragmentRefs } from "relay-runtime"; export type UserFrag1 = { readonly __typename: "User"; readonly " $refType": "UserFrag1"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "UserFrag1"; }; export type UserFrag1$data = UserFrag1; export type UserFrag1$key = { @@ -2440,11 +2398,6 @@ import { FragmentRefs } from "relay-runtime"; export type UserFrag2 = { readonly __typename: "User"; readonly " $refType": "UserFrag2"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "UserFrag2"; }; export type UserFrag2$data = UserFrag2; export type UserFrag2$key = { @@ -2600,11 +2553,6 @@ import { FragmentRefs } from "relay-runtime"; export type SomeFragment = { readonly __typename: "User"; readonly " $refType": "SomeFragment"; -} | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; - readonly " $refType": "SomeFragment"; }; export type SomeFragment$data = SomeFragment; export type SomeFragment$key = { @@ -3908,20 +3856,27 @@ export type RelayClientIDFieldQueryResponse = { readonly __typename: string; readonly id: string; } | null; - readonly node: { - readonly __id: string; - readonly __typename: string; - readonly id: string; - readonly commentBody?: { + readonly node: (({ + readonly __typename: "Comment"; + readonly commentBody: (({ + readonly __typename: "PlainCommentBody"; readonly __id: string; - readonly __typename: "PlainCommentBody" | "MarkdownCommentBody"; - readonly text?: { + readonly text: { readonly __id: string; readonly __typename: string; readonly text: string | null; } | null; - } | null; - } | null; + } | { + readonly __typename: "MarkdownCommentBody"; + }) & { + readonly __id: string; + }) | null; + } | { + readonly __typename: "Feedback" | "Page" | "PhotoStory" | "Story" | "User"; + }) & { + readonly __id: string; + readonly id: string; + }) | null; }; export type RelayClientIDFieldQuery = { readonly response: RelayClientIDFieldQueryResponse; @@ -4114,9 +4069,7 @@ export type TypenameInsideWithOverlappingFields = { readonly uri: string | null; } | null; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "ExtraUser"; }) | null; readonly " $refType": "TypenameInsideWithOverlappingFields"; }; @@ -4214,18 +4167,15 @@ fragment TypenameAliases on Actor { ~~~~~~~~~~ OUTPUT ~~~~~~~~~~ // TypenameInside.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameInside = { +export type TypenameInside = ({ readonly __typename: "User"; readonly firstName: string | null; - readonly " $refType": "TypenameInside"; } | { readonly __typename: "Page"; readonly username: string | null; - readonly " $refType": "TypenameInside"; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "ExtraUser"; +}) & { readonly " $refType": "TypenameInside"; }; export type TypenameInside$data = TypenameInside; @@ -4237,18 +4187,15 @@ export type TypenameInside$key = { // TypenameOutside.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameOutside = { +export type TypenameOutside = ({ readonly __typename: "User"; readonly firstName: string | null; - readonly " $refType": "TypenameOutside"; } | { readonly __typename: "Page"; readonly username: string | null; - readonly " $refType": "TypenameOutside"; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly __typename: "%other"; + readonly __typename: "ExtraUser"; +}) & { readonly " $refType": "TypenameOutside"; }; export type TypenameOutside$data = TypenameOutside; @@ -4260,15 +4207,21 @@ export type TypenameOutside$key = { // TypenameOutsideWithAbstractType.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameOutsideWithAbstractType = { - readonly __typename: string; +export type TypenameOutsideWithAbstractType = ({ + readonly __typename: "User"; + readonly firstName: string | null; + readonly address: { + readonly street: string | null; + readonly city: string | null; + } | null; +} | { + readonly __typename: "Comment" | "Feedback" | "Page" | "PhotoStory" | "Story"; +}) & { readonly username?: string | null; readonly address?: { readonly city: string | null; readonly country: string | null; - readonly street?: string | null; } | null; - readonly firstName?: string | null; readonly " $refType": "TypenameOutsideWithAbstractType"; }; export type TypenameOutsideWithAbstractType$data = TypenameOutsideWithAbstractType; @@ -4281,8 +4234,8 @@ export type TypenameOutsideWithAbstractType$key = { // TypenameWithoutSpreads.graphql import { FragmentRefs } from "relay-runtime"; export type TypenameWithoutSpreads = { - readonly firstName: string | null; readonly __typename: "User"; + readonly firstName: string | null; readonly " $refType": "TypenameWithoutSpreads"; }; export type TypenameWithoutSpreads$data = TypenameWithoutSpreads; @@ -4308,11 +4261,16 @@ export type TypenameWithoutSpreadsAbstractType$key = { // TypenameWithCommonSelections.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameWithCommonSelections = { - readonly __typename: string; +export type TypenameWithCommonSelections = ({ + readonly __typename: "User"; + readonly firstName: string | null; +} | { + readonly __typename: "Page"; + readonly username: string | null; +} | { + readonly __typename: "ExtraUser"; +}) & { readonly name: string | null; - readonly firstName?: string | null; - readonly username?: string | null; readonly " $refType": "TypenameWithCommonSelections"; }; export type TypenameWithCommonSelections$data = TypenameWithCommonSelections; @@ -4324,18 +4282,15 @@ export type TypenameWithCommonSelections$key = { // TypenameAlias.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameAlias = { +export type TypenameAlias = ({ readonly _typeAlias: "User"; readonly firstName: string | null; - readonly " $refType": "TypenameAlias"; } | { readonly _typeAlias: "Page"; readonly username: string | null; - readonly " $refType": "TypenameAlias"; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly _typeAlias: "%other"; + readonly _typeAlias: "ExtraUser"; +}) & { readonly " $refType": "TypenameAlias"; }; export type TypenameAlias$data = TypenameAlias; @@ -4347,23 +4302,18 @@ export type TypenameAlias$key = { // TypenameAliases.graphql import { FragmentRefs } from "relay-runtime"; -export type TypenameAliases = { +export type TypenameAliases = ({ readonly _typeAlias1: "User"; readonly _typeAlias2: "User"; readonly firstName: string | null; - readonly " $refType": "TypenameAliases"; } | { readonly _typeAlias1: "Page"; readonly _typeAlias2: "Page"; readonly username: string | null; - readonly " $refType": "TypenameAliases"; } | { - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly _typeAlias1: "%other"; - /*This will never be '%other', but we need some - value in case none of the concrete values match.*/ - readonly _typeAlias2: "%other"; + readonly _typeAlias1: "ExtraUser"; + readonly _typeAlias2: "ExtraUser"; +}) & { readonly " $refType": "TypenameAliases"; }; export type TypenameAliases$data = TypenameAliases;