Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Remove redundant constraints on IntrospectionQuery causing TS perf bottlenecks #26

Merged
merged 4 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tiny-cameras-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'gql.tada': patch
---

Remove redundant constraint on `IntrospectionQuery` data. When the full type is used as an `extends`, the input type (which can be a huge schema), is checked against this type, which forces a full evaluation. This means that TypeScript may spend multiple seconds in `recursiveTypeRelatedTo`. This work has been eliminated and should help performance.
38 changes: 0 additions & 38 deletions src/__tests__/fixtures/simpleSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ export type simpleSchema = {
Query: {
kind: 'OBJECT';
name: 'Query';
interfaces: never;
fields: {
todos: {
name: 'todos';
args: any;
type: {
kind: 'LIST';
name: null;
Expand All @@ -24,7 +22,6 @@ export type simpleSchema = {
};
latestTodo: {
name: 'latestTodo';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -37,7 +34,6 @@ export type simpleSchema = {
};
test: {
name: 'test';
args: any;
type: {
kind: 'UNION';
name: 'Search';
Expand Down Expand Up @@ -66,7 +62,6 @@ export type simpleSchema = {
fields: {
id: {
name: 'id';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -79,7 +74,6 @@ export type simpleSchema = {
};
text: {
name: 'text';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -92,7 +86,6 @@ export type simpleSchema = {
};
complete: {
name: 'complete';
args: any;
type: {
kind: 'SCALAR';
name: 'Boolean';
Expand All @@ -101,7 +94,6 @@ export type simpleSchema = {
};
author: {
name: 'author';
args: any;
type: {
kind: 'OBJECT';
name: 'Author';
Expand All @@ -110,15 +102,13 @@ export type simpleSchema = {
};
maxLength: {
name: 'maxLength';
args: any;
type: {
kind: 'SCALAR';
name: 'Int';
ofType: null;
};
};
};
interfaces: 'ITodo';
};

BigTodo: {
Expand All @@ -127,7 +117,6 @@ export type simpleSchema = {
fields: {
id: {
name: 'id';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -140,7 +129,6 @@ export type simpleSchema = {
};
text: {
name: 'text';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -153,7 +141,6 @@ export type simpleSchema = {
};
complete: {
name: 'complete';
args: any;
type: {
kind: 'SCALAR';
name: 'Boolean';
Expand All @@ -162,7 +149,6 @@ export type simpleSchema = {
};
author: {
name: 'author';
args: any;
type: {
kind: 'OBJECT';
name: 'Author';
Expand All @@ -171,26 +157,22 @@ export type simpleSchema = {
};
wallOfText: {
name: 'wallOfText';
args: any;
type: {
kind: 'SCALAR';
name: 'String';
ofType: null;
};
};
};
interfaces: 'ITodo';
};

ITodo: {
kind: 'INTERFACE';
name: 'ITodo';
interfaces: never;
possibleTypes: 'BigTodo' | 'SmallTodo';
fields: {
id: {
name: 'id';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -203,7 +185,6 @@ export type simpleSchema = {
};
text: {
name: 'text';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -216,7 +197,6 @@ export type simpleSchema = {
};
complete: {
name: 'complete';
args: any;
type: {
kind: 'SCALAR';
name: 'Boolean';
Expand All @@ -225,7 +205,6 @@ export type simpleSchema = {
};
author: {
name: 'author';
args: any;
type: {
kind: 'OBJECT';
name: 'Author';
Expand Down Expand Up @@ -274,11 +253,9 @@ export type simpleSchema = {
NoTodosError: {
kind: 'OBJECT';
name: 'NoTodosError';
interfaces: never;
fields: {
message: {
name: 'message';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -295,11 +272,9 @@ export type simpleSchema = {
Todo: {
kind: 'OBJECT';
name: 'Todo';
interfaces: never;
fields: {
id: {
name: 'id';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -312,7 +287,6 @@ export type simpleSchema = {
};
text: {
name: 'text';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -325,7 +299,6 @@ export type simpleSchema = {
};
complete: {
name: 'complete';
args: any;
type: {
kind: 'SCALAR';
name: 'Boolean';
Expand All @@ -334,7 +307,6 @@ export type simpleSchema = {
};
test: {
name: 'test';
args: any;
type: {
kind: 'ENUM';
name: 'test';
Expand All @@ -343,7 +315,6 @@ export type simpleSchema = {
};
author: {
name: 'author';
args: any;
type: {
kind: 'OBJECT';
name: 'Author';
Expand Down Expand Up @@ -376,11 +347,9 @@ export type simpleSchema = {
Author: {
kind: 'OBJECT';
name: 'Author';
interfaces: never;
fields: {
id: {
name: 'id';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -393,7 +362,6 @@ export type simpleSchema = {
};
name: {
name: 'name';
args: any;
type: {
kind: 'NON_NULL';
name: null;
Expand All @@ -406,7 +374,6 @@ export type simpleSchema = {
};
known: {
name: 'known';
args: any;
type: {
kind: 'SCALAR';
name: 'Boolean';
Expand All @@ -419,11 +386,9 @@ export type simpleSchema = {
Mutation: {
kind: 'OBJECT';
name: 'Mutation';
interfaces: never;
fields: {
toggleTodo: {
name: 'toggleTodo';
args: any;
type: {
kind: 'OBJECT';
name: 'Todo';
Expand All @@ -432,7 +397,6 @@ export type simpleSchema = {
};
updateTodo: {
name: 'updateTodo';
args: any;
type: {
kind: 'SCALAR';
name: 'Boolean';
Expand All @@ -445,11 +409,9 @@ export type simpleSchema = {
Subscription: {
kind: 'OBJECT';
name: 'Subscription';
interfaces: never;
fields: {
newTodo: {
name: 'newTodo';
args: any;
type: {
kind: 'OBJECT';
name: 'Todo';
Expand Down
42 changes: 23 additions & 19 deletions src/introspection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ interface IntrospectionSchema {
readonly queryType: IntrospectionNamedTypeRef;
readonly mutationType?: IntrospectionNamedTypeRef | null;
readonly subscriptionType?: IntrospectionNamedTypeRef | null;
readonly types: readonly IntrospectionType[];
/* Usually this would be:
| IntrospectionScalarType
| IntrospectionObjectType
| IntrospectionInterfaceType
| IntrospectionUnionType
| IntrospectionEnumType
| IntrospectionInputObjectType;
However, this forces TypeScript to evaluate the type of an
entire introspection query, rather than accept its shape as-is.
So, instead, we constrain it to `any` here.
*/
readonly types: readonly any[];
}

export type IntrospectionType =
| IntrospectionScalarType
| IntrospectionObjectType
| IntrospectionInterfaceType
| IntrospectionUnionType
| IntrospectionEnumType
| IntrospectionInputObjectType;

interface IntrospectionScalarType {
readonly kind: 'SCALAR';
readonly name: string;
Expand All @@ -37,16 +40,20 @@ interface IntrospectionScalarType {
export interface IntrospectionObjectType {
readonly kind: 'OBJECT';
readonly name: string;
readonly fields: readonly IntrospectionField[];
readonly interfaces: readonly IntrospectionNamedTypeRef[] | never;
// Usually this would be `IntrospectionField`.
// However, to save TypeScript some work, instead, we constraint it to `any` here.
readonly fields: readonly any[];
// The `interfaces` field isn't used. It's omitted here
}

interface IntrospectionInterfaceType {
readonly kind: 'INTERFACE';
readonly name: string;
readonly fields: readonly IntrospectionField[];
// Usually this would be `IntrospectionField`.
// However, to save TypeScript some work, instead, we constraint it to `any` here.
readonly fields: readonly any[];
readonly possibleTypes: readonly IntrospectionNamedTypeRef[];
readonly interfaces?: readonly IntrospectionNamedTypeRef[] | null;
// The `interfaces` field isn't used. It's omitted here
}

interface IntrospectionUnionType {
Expand Down Expand Up @@ -92,8 +99,8 @@ export interface IntrospectionNamedTypeRef {

export interface IntrospectionField {
readonly name: string;
readonly args: readonly IntrospectionInputValue[];
readonly type: IntrospectionTypeRef;
// The `args` field isn't used. It's omitted here
}

interface IntrospectionInputValue {
Expand Down Expand Up @@ -132,14 +139,12 @@ type mapField<T> = T extends IntrospectionField
? {
name: T['name'];
type: T['type'];
args: any;
}
: never;

export type mapObject<T extends IntrospectionObjectType> = {
kind: 'OBJECT';
name: T['name'];
interfaces: T['interfaces'][number]['name'];
fields: obj<{
[P in T['fields'][number]['name']]: T['fields'][number] extends infer Field
? Field extends { readonly name: P }
Expand All @@ -158,7 +163,6 @@ export type mapInputObject<T extends IntrospectionInputObjectType> = {
type mapInterface<T extends IntrospectionInterfaceType> = {
kind: 'INTERFACE';
name: T['name'];
interfaces: T['interfaces'] extends readonly any[] ? T['interfaces'][number]['name'] : never;
possibleTypes: T['possibleTypes'][number]['name'];
fields: obj<{
[P in T['fields'][number]['name']]: T['fields'][number] extends infer Field
Expand Down Expand Up @@ -224,8 +228,8 @@ export type ScalarsLike = {

export type IntrospectionLikeType = {
query: string;
mutation: string | never;
subscription: string | never;
mutation?: any;
subscription?: any;
types: { [name: string]: any };
};

Expand Down
Loading