diff --git a/.changeset/mean-geckos-dream.md b/.changeset/mean-geckos-dream.md new file mode 100644 index 00000000..35f813a0 --- /dev/null +++ b/.changeset/mean-geckos-dream.md @@ -0,0 +1,5 @@ +--- +'gql.tada': patch +--- + +Fix `__typename` literal string not being exact and instead a union of possible types, when the `__typename` field is put onto an abstract type’s selection set. diff --git a/src/__tests__/selection.test-d.ts b/src/__tests__/selection.test-d.ts index 69bb28ba..c64e75a1 100644 --- a/src/__tests__/selection.test-d.ts +++ b/src/__tests__/selection.test-d.ts @@ -301,6 +301,32 @@ test('infers unions and interfaces correctly', () => { expectTypeOf().toEqualTypeOf(); }); +test('infers __typename on union unambiguously', () => { + type query = parseDocument; + + type actual = getDocumentType; + type expected = { + test: + | { + __typename: 'SmallTodo'; + } + | { + __typename: 'BigTodo'; + wallOfText: string | null; + } + | null; + }; + + expectTypeOf().toEqualTypeOf(); +}); + test('infers queries from GitHub introspection schema', () => { type schema = mapIntrospection< typeof import('./fixtures/githubIntrospection').githubIntrospection diff --git a/src/selection.ts b/src/selection.ts index 806b3f46..85308c6c 100644 --- a/src/selection.ts +++ b/src/selection.ts @@ -65,10 +65,10 @@ type isOptionalRec = Directives extends readonly [infer Directive, ...infer Rest] ? Directive extends { kind: Kind.DIRECTIVE; name: any } ? Directive['name']['value'] extends 'include' | 'skip' | 'defer' - ? false + ? true : isOptionalRec : isOptionalRec - : true; + : false; type getFieldAlias = Node['alias'] extends undefined ? Node['name']['value'] @@ -114,7 +114,7 @@ type getSpreadSubtype< type getTypenameOfType = Type extends { possibleTypes: any; } - ? Type['possibleTypes'] + ? Type['name'] | Type['possibleTypes'] : Type['name']; type getSelection< @@ -122,44 +122,9 @@ type getSelection< Type extends ObjectLikeType, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, -> = obj< - getFieldsSelectionRec & - getFragmentsSelection ->; +> = obj>; -type getFieldsSelectionRec< - Selections extends readonly unknown[], - Type extends ObjectLikeType, - Introspection extends IntrospectionLikeType, - Fragments extends { [name: string]: any }, -> = Selections extends readonly [infer Selection, ...infer Rest] - ? (Selection extends FieldNode - ? isOptionalRec extends true - ? { - [Prop in getFieldAlias]: Selection['name']['value'] extends '__typename' - ? getTypenameOfType - : unwrapType< - Type['fields'][Selection['name']['value']]['type'], - Selection['selectionSet'], - Introspection, - Fragments - >; - } - : { - [Prop in getFieldAlias]?: Selection['name']['value'] extends '__typename' - ? getTypenameOfType - : unwrapType< - Type['fields'][Selection['name']['value']]['type'], - Selection['selectionSet'], - Introspection, - Fragments - >; - } - : {}) & - getFieldsSelectionRec - : {}; - -type _getFragmentsSelectionRec< +type _getPossibleTypeSelectionRec< Selections extends readonly unknown[], PossibleType extends string, Type extends ObjectLikeType, @@ -169,26 +134,48 @@ type _getFragmentsSelectionRec< ? (Node extends FragmentSpreadNode | InlineFragmentNode ? getSpreadSubtype extends infer Subtype extends ObjectLikeType - ? PossibleType extends Subtype['name'] | getTypenameOfType + ? PossibleType extends getTypenameOfType ? - | (isOptionalRec extends true ? never : {}) + | (isOptionalRec extends true ? {} : never) | getFragmentSelection : {} : Node extends FragmentSpreadNode ? makeUndefinedFragmentRef : {} - : {}) & - _getFragmentsSelectionRec + : Node extends FieldNode + ? isOptionalRec extends true + ? { + [Prop in getFieldAlias]?: Node['name']['value'] extends '__typename' + ? PossibleType + : unwrapType< + Type['fields'][Node['name']['value']]['type'], + Node['selectionSet'], + Introspection, + Fragments + >; + } + : { + [Prop in getFieldAlias]: Node['name']['value'] extends '__typename' + ? PossibleType + : unwrapType< + Type['fields'][Node['name']['value']]['type'], + Node['selectionSet'], + Introspection, + Fragments + >; + } + : {}) & + _getPossibleTypeSelectionRec : {}; -type getFragmentsSelection< +type getPossibleTypesSelection< Selections extends readonly unknown[], Type extends ObjectLikeType, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, > = Type extends { kind: 'UNION' | 'INTERFACE'; possibleTypes: any } ? objValues<{ - [PossibleType in Type['possibleTypes']]: _getFragmentsSelectionRec< + [PossibleType in Type['possibleTypes']]: _getPossibleTypeSelectionRec< Selections, PossibleType, Type, @@ -197,7 +184,7 @@ type getFragmentsSelection< >; }> : Type extends { kind: 'OBJECT'; name: any } - ? _getFragmentsSelectionRec + ? _getPossibleTypeSelectionRec : {}; type getOperationSelectionType<