Skip to content

Commit

Permalink
fix: Fix __typename fields on abstract selections not being exact (#11)
Browse files Browse the repository at this point in the history
* Add failing test for __typename on abstract types

* Handle __typename fields in possible fragment iterator

* Add optional field handling to new __typename field

* Remove getFieldsSelectionRec and reconcile code paths

* Refactor getTypenameOfType

* Rename utility

* Fix isOptionalRec boolean result being inverted

* Add changeset
  • Loading branch information
kitten authored Jan 16, 2024
1 parent 50b43ba commit 7986c54
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 47 deletions.
5 changes: 5 additions & 0 deletions .changeset/mean-geckos-dream.md
Original file line number Diff line number Diff line change
@@ -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.
26 changes: 26 additions & 0 deletions src/__tests__/selection.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,32 @@ test('infers unions and interfaces correctly', () => {
expectTypeOf<expected>().toEqualTypeOf<actual>();
});

test('infers __typename on union unambiguously', () => {
type query = parseDocument</* GraphQL */ `
query {
test {
__typename
... on BigTodo { wallOfText }
}
}
`>;

type actual = getDocumentType<query, schema>;
type expected = {
test:
| {
__typename: 'SmallTodo';
}
| {
__typename: 'BigTodo';
wallOfText: string | null;
}
| null;
};

expectTypeOf<expected>().toEqualTypeOf<actual>();
});

test('infers queries from GitHub introspection schema', () => {
type schema = mapIntrospection<
typeof import('./fixtures/githubIntrospection').githubIntrospection
Expand Down
81 changes: 34 additions & 47 deletions src/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ type isOptionalRec<Directives extends readonly unknown[] | undefined> =
Directives extends readonly [infer Directive, ...infer Rest]
? Directive extends { kind: Kind.DIRECTIVE; name: any }
? Directive['name']['value'] extends 'include' | 'skip' | 'defer'
? false
? true
: isOptionalRec<Rest>
: isOptionalRec<Rest>
: true;
: false;

type getFieldAlias<Node extends FieldNode> = Node['alias'] extends undefined
? Node['name']['value']
Expand Down Expand Up @@ -114,52 +114,17 @@ type getSpreadSubtype<
type getTypenameOfType<Type extends ObjectLikeType> = Type extends {
possibleTypes: any;
}
? Type['possibleTypes']
? Type['name'] | Type['possibleTypes']
: Type['name'];

type getSelection<
Selections extends readonly any[],
Type extends ObjectLikeType,
Introspection extends IntrospectionLikeType,
Fragments extends { [name: string]: any },
> = obj<
getFieldsSelectionRec<Selections, Type, Introspection, Fragments> &
getFragmentsSelection<Selections, Type, Introspection, Fragments>
>;
> = obj<getPossibleTypesSelection<Selections, Type, Introspection, Fragments>>;

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<Selection['directives']> extends true
? {
[Prop in getFieldAlias<Selection>]: Selection['name']['value'] extends '__typename'
? getTypenameOfType<Type>
: unwrapType<
Type['fields'][Selection['name']['value']]['type'],
Selection['selectionSet'],
Introspection,
Fragments
>;
}
: {
[Prop in getFieldAlias<Selection>]?: Selection['name']['value'] extends '__typename'
? getTypenameOfType<Type>
: unwrapType<
Type['fields'][Selection['name']['value']]['type'],
Selection['selectionSet'],
Introspection,
Fragments
>;
}
: {}) &
getFieldsSelectionRec<Rest, Type, Introspection, Fragments>
: {};

type _getFragmentsSelectionRec<
type _getPossibleTypeSelectionRec<
Selections extends readonly unknown[],
PossibleType extends string,
Type extends ObjectLikeType,
Expand All @@ -169,26 +134,48 @@ type _getFragmentsSelectionRec<
? (Node extends FragmentSpreadNode | InlineFragmentNode
? getSpreadSubtype<Node, Type, Introspection, Fragments> extends infer Subtype extends
ObjectLikeType
? PossibleType extends Subtype['name'] | getTypenameOfType<Subtype>
? PossibleType extends getTypenameOfType<Subtype>
?
| (isOptionalRec<Node['directives']> extends true ? never : {})
| (isOptionalRec<Node['directives']> extends true ? {} : never)
| getFragmentSelection<Node, Subtype, Introspection, Fragments>
: {}
: Node extends FragmentSpreadNode
? makeUndefinedFragmentRef<Node['name']['value']>
: {}
: {}) &
_getFragmentsSelectionRec<Rest, PossibleType, Type, Introspection, Fragments>
: Node extends FieldNode
? isOptionalRec<Node['directives']> extends true
? {
[Prop in getFieldAlias<Node>]?: Node['name']['value'] extends '__typename'
? PossibleType
: unwrapType<
Type['fields'][Node['name']['value']]['type'],
Node['selectionSet'],
Introspection,
Fragments
>;
}
: {
[Prop in getFieldAlias<Node>]: Node['name']['value'] extends '__typename'
? PossibleType
: unwrapType<
Type['fields'][Node['name']['value']]['type'],
Node['selectionSet'],
Introspection,
Fragments
>;
}
: {}) &
_getPossibleTypeSelectionRec<Rest, PossibleType, Type, Introspection, Fragments>
: {};

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,
Expand All @@ -197,7 +184,7 @@ type getFragmentsSelection<
>;
}>
: Type extends { kind: 'OBJECT'; name: any }
? _getFragmentsSelectionRec<Selections, Type['name'], Type, Introspection, Fragments>
? _getPossibleTypeSelectionRec<Selections, Type['name'], Type, Introspection, Fragments>
: {};

type getOperationSelectionType<
Expand Down

0 comments on commit 7986c54

Please sign in to comment.