Skip to content

Commit

Permalink
fix: Improve selection and variables performance (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
kitten authored Jan 22, 2024
1 parent 0bb2dac commit 67a8cae
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 102 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-bats-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'gql.tada': patch
---

Improve performance of selection and variables inference.
66 changes: 59 additions & 7 deletions src/__tests__/selection.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<document, schema>;
type Input = getVariablesType<document, schema>;
Expand Down
93 changes: 34 additions & 59 deletions src/selection.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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['ofType'], SelectionSet, Introspection, Fragments>
: Type extends IntrospectionListTypeRef
: Type extends { readonly kind: 'LIST'; readonly ofType: any }
? Array<unwrapType<Type['ofType'], SelectionSet, Introspection, Fragments>>
: Type extends IntrospectionNamedTypeRef
: Type extends { readonly name: string }
? Introspection['types'][Type['name']] extends ObjectLikeType
? SelectionSet extends { kind: Kind.SELECTION_SET; selections: any }
? getSelection<
Expand All @@ -48,51 +35,41 @@ 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<Type['ofType'], SelectionSet, Introspection, Fragments>
: _unwrapTypeRec<Type['ofType'], SelectionSet, Introspection, Fragments>
: TypeDirective extends 'required'
? _unwrapTypeRec<Type, SelectionSet, Introspection, Fragments>
: null | _unwrapTypeRec<Type, SelectionSet, Introspection, Fragments>;

type getTypeDirective<Directives extends readonly any[] | undefined> = 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<Rest>
: getTypeDirective<Rest>
type getTypeDirective<Node> = 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 any[] | undefined> = Directives extends readonly [
infer Directive,
...infer Rest,
]
? Directive extends { kind: Kind.DIRECTIVE; name: any }
? Directive['name']['value'] extends 'include' | 'skip' | 'defer'
? true
: isOptionalRec<Rest>
: isOptionalRec<Rest>
type isOptional<Node> = Node extends { directives: any[] }
? Node['directives'][number]['name']['value'] & ('include' | 'skip' | 'defer') extends never
? false
: true
: false;

type getFieldAlias<Node extends FieldNode> = Node['alias'] extends undefined
type getFieldAlias<Node> = 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 },
Expand All @@ -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 },
Expand All @@ -126,14 +103,12 @@ type getSpreadSubtype<
: void
: void;

type getTypenameOfType<Type extends ObjectLikeType> = Type extends {
possibleTypes: any;
}
? Type['name'] | Type['possibleTypes']
: Type['name'];
type getTypenameOfType<Type> =
| (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 },
Expand All @@ -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<Node, Type, Introspection, Fragments> extends infer Subtype extends
ObjectLikeType
? PossibleType extends getTypenameOfType<Subtype>
?
| (isOptionalRec<Node['directives']> extends true ? {} : never)
| (isOptional<Node> extends true ? {} : never)
| getFragmentSelection<Node, Subtype, Introspection, Fragments>
: {}
: Node extends FragmentSpreadNode
: Node extends { kind: Kind.FRAGMENT_SPREAD; name: any }
? makeUndefinedFragmentRef<Node['name']['value']>
: {}
: Node extends FieldNode
? isOptionalRec<Node['directives']> extends true
: Node extends { kind: Kind.FIELD; name: any; selectionSet: any }
? isOptional<Node> extends true
? {
[Prop in getFieldAlias<Node>]?: Node['name']['value'] extends '__typename'
? PossibleType
Expand All @@ -181,7 +156,7 @@ type _getPossibleTypeSelectionRec<
Node['selectionSet'],
Introspection,
Fragments,
getTypeDirective<Node['directives']>
getTypeDirective<Node>
>;
}
: {
Expand All @@ -192,7 +167,7 @@ type _getPossibleTypeSelectionRec<
Node['selectionSet'],
Introspection,
Fragments,
getTypeDirective<Node['directives']>
getTypeDirective<Node>
>;
}
: {}) &
Expand Down
67 changes: 31 additions & 36 deletions src/variables.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,71 @@
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<InputField['type'], Introspection> }
: { [Name in InputField['name']]?: unwrapType<InputField['type'], Introspection> }
: {}) &
getInputObjectTypeRec<Rest, Introspection>
: {};

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<getInputObjectTypeRec<InputFields, Introspection>>
? Introspection['types'][TypeName] extends { kind: 'SCALAR' | 'ENUM'; type: any }
? Introspection['types'][TypeName]['type']
: Introspection['types'][TypeName] extends { kind: 'INPUT_OBJECT'; inputFields: any }
? obj<getInputObjectTypeRec<Introspection['types'][TypeName]['inputFields'], Introspection>>
: never
: unknown;

type _unwrapTypeRec<
TypeRef extends TypeNode,
Introspection extends IntrospectionLikeType,
> = TypeRef extends { kind: 'NON_NULL' }
type _unwrapTypeRec<TypeRef, Introspection extends IntrospectionLikeType> = TypeRef extends {
kind: 'NON_NULL';
ofType: any;
}
? _unwrapTypeRec<TypeRef['ofType'], Introspection>
: TypeRef extends { kind: 'LIST' }
: TypeRef extends { kind: 'LIST'; ofType: any }
? Array<unwrapType<TypeRef['ofType'], Introspection>>
: TypeRef extends { name: any }
? getScalarType<TypeRef['name'], Introspection>
: unknown;

type unwrapType<Type extends TypeNode, Introspection extends IntrospectionLikeType> = Type extends {
type unwrapType<Type, Introspection extends IntrospectionLikeType> = Type extends {
kind: 'NON_NULL';
ofType: any;
}
? _unwrapTypeRec<Type['ofType'], Introspection>
: null | _unwrapTypeRec<Type, Introspection>;

type _nwrapTypeRefRec<
Type extends TypeNode,
Introspection extends IntrospectionLikeType,
> = Type extends { kind: Kind.NON_NULL_TYPE }
? _nwrapTypeRefRec<Type['type'], Introspection>
: Type extends { kind: Kind.LIST_TYPE }
type _unwrapTypeRefRec<Type, Introspection extends IntrospectionLikeType> = Type extends {
kind: Kind.NON_NULL_TYPE;
type: any;
}
? _unwrapTypeRefRec<Type['type'], Introspection>
: Type extends { kind: Kind.LIST_TYPE; type: any }
? Array<unwrapTypeRef<Type['type'], Introspection>>
: Type extends { kind: Kind.NAMED_TYPE; name: any }
? getScalarType<Type['name']['value'], Introspection>
: unknown;

type unwrapTypeRef<
Type extends TypeNode,
Introspection extends IntrospectionLikeType,
> = Type extends { kind: Kind.NON_NULL_TYPE }
? _nwrapTypeRefRec<Type['type'], Introspection>
: null | _nwrapTypeRefRec<Type, Introspection>;
type unwrapTypeRef<Type, Introspection extends IntrospectionLikeType> = Type extends {
kind: Kind.NON_NULL_TYPE;
type: any;
}
? _unwrapTypeRefRec<Type['type'], Introspection>
: null | _unwrapTypeRefRec<Type, Introspection>;

type getVariablesRec<
Variables extends readonly unknown[],
Introspection extends IntrospectionLikeType,
> = Variables extends [infer Variable, ...infer Rest]
type getVariablesRec<Variables, Introspection extends IntrospectionLikeType> = 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 } }
? {
Expand Down

0 comments on commit 67a8cae

Please sign in to comment.