diff --git a/.changeset/four-bats-impress.md b/.changeset/four-bats-impress.md new file mode 100644 index 00000000..e05ef0e2 --- /dev/null +++ b/.changeset/four-bats-impress.md @@ -0,0 +1,5 @@ +--- +'gql.tada': patch +--- + +Improve performance of selection and variables inference. diff --git a/src/__tests__/selection.bench.ts b/src/__tests__/selection.bench.ts index 1b6ff9f3..a992733f 100644 --- a/src/__tests__/selection.bench.ts +++ b/src/__tests__/selection.bench.ts @@ -7,18 +7,70 @@ describe('TypedDocument', () => { ...ts.readSourceFolders(['.']), 'simpleSchema.ts': ts.readFileFromRoot('src/__tests__/fixtures/simpleSchema.ts'), 'index.ts': ` + import { Kind, OperationTypeNode } from '@0no-co/graphql.web'; import { simpleSchema as schema } from './simpleSchema'; import { parseDocument } from './parser'; import { getDocumentType } from './selection'; import { getVariablesType } from './variables'; - type document = parseDocument<\` - query ($org: String!, $repo: String!) { - todos { - id - } - } - \`>; + type document = { + kind: Kind.DOCUMENT; + definitions: [{ + kind: Kind.OPERATION_DEFINITION; + operation: OperationTypeNode.QUERY; + name: undefined; + variableDefinitions: [{ + kind: Kind.VARIABLE_DEFINITION; + variable: { + kind: Kind.VARIABLE; + name: { + kind: Kind.NAME; + value: 'test'; + }; + }; + type: { + kind: Kind.NON_NULL_TYPE; + type: { + kind: Kind.NAMED_TYPE; + name: { + kind: Kind.NAME; + value: 'String'; + }; + }; + }; + defaultValue: undefined; + directives: []; + }]; + selectionSet: { + kind: Kind.SELECTION_SET; + selections: [{ + kind: Kind.FIELD; + alias: undefined; + name: { + kind: Kind.NAME; + value: 'todos'; + }; + directives: []; + arguments: []; + selectionSet: { + kind: Kind.SELECTION_SET; + selections: [{ + kind: Kind.FIELD; + alias: undefined; + name: { + kind: Kind.NAME; + value: 'id'; + }; + selectionSet: undefined; + directives: []; + arguments: []; + }]; + }; + }] + }; + directives: []; + }]; + }; type Result = getDocumentType; type Input = getVariablesType; diff --git a/src/selection.ts b/src/selection.ts index 72d6d435..ef5e2242 100644 --- a/src/selection.ts +++ b/src/selection.ts @@ -1,23 +1,10 @@ -import type { - Kind, - FieldNode, - FragmentSpreadNode, - InlineFragmentNode, - NameNode, -} from '@0no-co/graphql.web'; +import type { Kind } from '@0no-co/graphql.web'; import type { obj, objValues } from './utils'; import type { DocumentNodeLike } from './parser'; import type { $tada, makeUndefinedFragmentRef } from './namespace'; - -import type { - IntrospectionListTypeRef, - IntrospectionNamedTypeRef, - IntrospectionNonNullTypeRef, - IntrospectionTypeRef, - IntrospectionLikeType, -} from './introspection'; +import type { IntrospectionLikeType } from './introspection'; type ObjectLikeType = { kind: 'OBJECT' | 'INTERFACE' | 'UNION'; @@ -26,15 +13,15 @@ type ObjectLikeType = { }; type _unwrapTypeRec< - Type extends IntrospectionTypeRef, + Type, SelectionSet, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, -> = Type extends IntrospectionNonNullTypeRef +> = Type extends { readonly kind: 'NON_NULL'; readonly ofType: any } ? _unwrapTypeRec - : Type extends IntrospectionListTypeRef + : Type extends { readonly kind: 'LIST'; readonly ofType: any } ? Array> - : Type extends IntrospectionNamedTypeRef + : Type extends { readonly name: string } ? Introspection['types'][Type['name']] extends ObjectLikeType ? SelectionSet extends { kind: Kind.SELECTION_SET; selections: any } ? getSelection< @@ -48,12 +35,12 @@ type _unwrapTypeRec< : unknown; type unwrapType< - Type extends IntrospectionTypeRef, + Type, SelectionSet, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, TypeDirective = void, -> = Type extends IntrospectionNonNullTypeRef +> = Type extends { readonly kind: 'NON_NULL'; readonly ofType: any } ? TypeDirective extends 'optional' ? null | _unwrapTypeRec : _unwrapTypeRec @@ -61,38 +48,28 @@ type unwrapType< ? _unwrapTypeRec : null | _unwrapTypeRec; -type getTypeDirective = Directives extends readonly [ - infer Directive, - ...infer Rest, -] - ? Directive extends { kind: Kind.DIRECTIVE; name: any } - ? Directive['name']['value'] extends 'required' | '_required' - ? 'required' - : Directive['name']['value'] extends 'optional' | '_optional' - ? 'optional' - : getTypeDirective - : getTypeDirective +type getTypeDirective = Node extends { directives: any[] } + ? Node['directives'][number]['name']['value'] & ('required' | '_required') extends never + ? Node['directives'][number]['name']['value'] & ('optional' | '_optional') extends never + ? void + : 'optional' + : 'required' : void; -type isOptionalRec = Directives extends readonly [ - infer Directive, - ...infer Rest, -] - ? Directive extends { kind: Kind.DIRECTIVE; name: any } - ? Directive['name']['value'] extends 'include' | 'skip' | 'defer' - ? true - : isOptionalRec - : isOptionalRec +type isOptional = Node extends { directives: any[] } + ? Node['directives'][number]['name']['value'] & ('include' | 'skip' | 'defer') extends never + ? false + : true : false; -type getFieldAlias = Node['alias'] extends undefined +type getFieldAlias = Node extends { alias: undefined; name: any } ? Node['name']['value'] - : Node['alias'] extends NameNode + : Node extends { alias: any } ? Node['alias']['value'] : never; type getFragmentSelection< - Node extends { kind: Kind.FRAGMENT_SPREAD | Kind.INLINE_FRAGMENT }, + Node, Type extends ObjectLikeType, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, @@ -112,7 +89,7 @@ type getFragmentSelection< : {}; type getSpreadSubtype< - Node extends { kind: Kind.FRAGMENT_SPREAD | Kind.INLINE_FRAGMENT }, + Node, BaseType extends ObjectLikeType, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, @@ -126,14 +103,12 @@ type getSpreadSubtype< : void : void; -type getTypenameOfType = Type extends { - possibleTypes: any; -} - ? Type['name'] | Type['possibleTypes'] - : Type['name']; +type getTypenameOfType = + | (Type extends { name: any } ? Type['name'] : never) + | (Type extends { possibleTypes: any } ? Type['possibleTypes'] : never); type getSelection< - Selections extends readonly any[], + Selections, Type extends ObjectLikeType, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, @@ -154,25 +129,25 @@ type getSelection< >; type _getPossibleTypeSelectionRec< - Selections extends readonly any[], + Selections, PossibleType extends string, Type extends ObjectLikeType, Introspection extends IntrospectionLikeType, Fragments extends { [name: string]: any }, > = Selections extends [infer Node, ...infer Rest] - ? (Node extends FragmentSpreadNode | InlineFragmentNode + ? (Node extends { kind: Kind.FRAGMENT_SPREAD | Kind.INLINE_FRAGMENT } ? getSpreadSubtype extends infer Subtype extends ObjectLikeType ? PossibleType extends getTypenameOfType ? - | (isOptionalRec extends true ? {} : never) + | (isOptional extends true ? {} : never) | getFragmentSelection : {} - : Node extends FragmentSpreadNode + : Node extends { kind: Kind.FRAGMENT_SPREAD; name: any } ? makeUndefinedFragmentRef : {} - : Node extends FieldNode - ? isOptionalRec extends true + : Node extends { kind: Kind.FIELD; name: any; selectionSet: any } + ? isOptional extends true ? { [Prop in getFieldAlias]?: Node['name']['value'] extends '__typename' ? PossibleType @@ -181,7 +156,7 @@ type _getPossibleTypeSelectionRec< Node['selectionSet'], Introspection, Fragments, - getTypeDirective + getTypeDirective >; } : { @@ -192,7 +167,7 @@ type _getPossibleTypeSelectionRec< Node['selectionSet'], Introspection, Fragments, - getTypeDirective + getTypeDirective >; } : {}) & diff --git a/src/variables.ts b/src/variables.ts index 5626cd66..e4e82e21 100644 --- a/src/variables.ts +++ b/src/variables.ts @@ -1,14 +1,14 @@ -import type { Kind, TypeNode } from '@0no-co/graphql.web'; +import type { Kind } from '@0no-co/graphql.web'; import type { IntrospectionLikeType } from './introspection'; import type { DocumentNodeLike } from './parser'; import type { obj } from './utils'; type getInputObjectTypeRec< - InputFields extends readonly unknown[], + InputFields, Introspection extends IntrospectionLikeType, > = InputFields extends [infer InputField, ...infer Rest] ? (InputField extends { name: any; type: any } - ? InputField extends { type: { kind: 'NON_NULL' } } + ? InputField['type'] extends { kind: 'NON_NULL' } ? { [Name in InputField['name']]: unwrapType } : { [Name in InputField['name']]?: unwrapType } : {}) & @@ -16,61 +16,56 @@ type getInputObjectTypeRec< : {}; type getScalarType< - TypeName extends string, + TypeName, Introspection extends IntrospectionLikeType, > = TypeName extends keyof Introspection['types'] - ? Introspection['types'][TypeName] extends { - kind: 'SCALAR' | 'ENUM'; - type: infer IntrospectionValueType; - } - ? IntrospectionValueType - : Introspection['types'][TypeName] extends { - kind: 'INPUT_OBJECT'; - inputFields: [...infer InputFields]; - } - ? obj> + ? Introspection['types'][TypeName] extends { kind: 'SCALAR' | 'ENUM'; type: any } + ? Introspection['types'][TypeName]['type'] + : Introspection['types'][TypeName] extends { kind: 'INPUT_OBJECT'; inputFields: any } + ? obj> : never : unknown; -type _unwrapTypeRec< - TypeRef extends TypeNode, - Introspection extends IntrospectionLikeType, -> = TypeRef extends { kind: 'NON_NULL' } +type _unwrapTypeRec = TypeRef extends { + kind: 'NON_NULL'; + ofType: any; +} ? _unwrapTypeRec - : TypeRef extends { kind: 'LIST' } + : TypeRef extends { kind: 'LIST'; ofType: any } ? Array> : TypeRef extends { name: any } ? getScalarType : unknown; -type unwrapType = Type extends { +type unwrapType = Type extends { kind: 'NON_NULL'; + ofType: any; } ? _unwrapTypeRec : null | _unwrapTypeRec; -type _nwrapTypeRefRec< - Type extends TypeNode, - Introspection extends IntrospectionLikeType, -> = Type extends { kind: Kind.NON_NULL_TYPE } - ? _nwrapTypeRefRec - : Type extends { kind: Kind.LIST_TYPE } +type _unwrapTypeRefRec = Type extends { + kind: Kind.NON_NULL_TYPE; + type: any; +} + ? _unwrapTypeRefRec + : Type extends { kind: Kind.LIST_TYPE; type: any } ? Array> : Type extends { kind: Kind.NAMED_TYPE; name: any } ? getScalarType : unknown; -type unwrapTypeRef< - Type extends TypeNode, - Introspection extends IntrospectionLikeType, -> = Type extends { kind: Kind.NON_NULL_TYPE } - ? _nwrapTypeRefRec - : null | _nwrapTypeRefRec; +type unwrapTypeRef = Type extends { + kind: Kind.NON_NULL_TYPE; + type: any; +} + ? _unwrapTypeRefRec + : null | _unwrapTypeRefRec; -type getVariablesRec< - Variables extends readonly unknown[], - Introspection extends IntrospectionLikeType, -> = Variables extends [infer Variable, ...infer Rest] +type getVariablesRec = Variables extends [ + infer Variable, + ...infer Rest, +] ? (Variable extends { kind: Kind.VARIABLE_DEFINITION; variable: any; type: any } ? Variable extends { defaultValue: undefined; type: { kind: Kind.NON_NULL_TYPE } } ? {