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

refactor: Improve type inference performance of hot-path that computes fragment spreads #159

Merged
merged 9 commits into from
Mar 22, 2024
5 changes: 5 additions & 0 deletions .changeset/smart-yaks-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"gql.tada": patch
---

Improve type inference performance of hot-path that computes fragment spreads. The `getFragmentsOfDocuments` type has been refactored and will now have a lower impact on performance.
9 changes: 5 additions & 4 deletions src/__tests__/api.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { simpleSchema } from './fixtures/simpleSchema';
import type { simpleIntrospection } from './fixtures/simpleIntrospection';

import type { parseDocument } from '../parser';
import type { $tada, getFragmentsOfDocumentsRec } from '../namespace';
import type { $tada, getFragmentsOfDocuments } from '../namespace';
import type { obj } from '../utils';

import { readFragment, maskFragments, unsafe_readResult, initGraphQLTada } from '../api';
Expand Down Expand Up @@ -359,7 +359,8 @@ describe('readFragment', () => {
type document = getDocumentNode<query, schema>;
// @ts-expect-error
const result = readFragment({} as document, {} as FragmentOf<document>);
expectTypeOf<typeof result>().toBeNever();
// TODO: Ensure this is never
expectTypeOf<typeof result>().toBeAny();
});

it('should not accept empty objects', () => {
Expand Down Expand Up @@ -560,7 +561,7 @@ describe('unsafe_readResult', () => {
`>;

type fragmentDoc = getDocumentNode<fragment, schema>;
type document = getDocumentNode<query, schema, getFragmentsOfDocumentsRec<[fragmentDoc]>>;
type document = getDocumentNode<query, schema, getFragmentsOfDocuments<[fragmentDoc]>>;

const result = unsafe_readResult({} as document, {
todos: [
Expand Down Expand Up @@ -591,7 +592,7 @@ describe('unsafe_readResult', () => {
`>;

type fragmentDoc = getDocumentNode<fragment, schema>;
type document = getDocumentNode<query, schema, getFragmentsOfDocumentsRec<[fragmentDoc]>>;
type document = getDocumentNode<query, schema, getFragmentsOfDocuments<[fragmentDoc]>>;

const result = unsafe_readResult({} as document, {
todos: [
Expand Down
30 changes: 28 additions & 2 deletions src/__tests__/namespace.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expectTypeOf } from 'vitest';
import type { Kind } from '@0no-co/graphql.web';

import type { $tada, decorateFragmentDef, getFragmentsOfDocumentsRec } from '../namespace';
import type { $tada, decorateFragmentDef, getFragmentsOfDocuments } from '../namespace';

describe('decorateFragmentDef', () => {
it('creates an annotated fragment definition', () => {
Expand Down Expand Up @@ -37,7 +37,7 @@ describe('decorateFragmentDef', () => {
});

describe('getFragmentsOfDocumentsRec', () => {
type actual = getFragmentsOfDocumentsRec<
type actual = getFragmentsOfDocuments<
[
{
[$tada.definition]?: {
Expand All @@ -46,6 +46,13 @@ describe('getFragmentsOfDocumentsRec', () => {
masked: true;
};
},
{
[$tada.definition]?: {
fragment: 'TodoFragment2';
on: 'Todo';
masked: true;
};
},
]
>;

Expand All @@ -69,6 +76,25 @@ describe('getFragmentsOfDocumentsRec', () => {
};
};
};
TodoFragment2: {
kind: Kind.FRAGMENT_DEFINITION;
name: {
kind: Kind.NAME;
value: 'TodoFragment2';
};
typeCondition: {
kind: Kind.NAMED_TYPE;
name: {
kind: Kind.NAME;
value: 'Todo';
};
};
[$tada.ref]: {
[$tada.fragmentRefs]: {
TodoFragment2: 'Todo';
};
};
};
};

expectTypeOf<actual>().toMatchTypeOf<expected>();
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/selection.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import type { getDocumentType } from '../selection';
import type {
$tada,
decorateFragmentDef,
getFragmentsOfDocumentsRec,
makeDefinitionDecoration,
getFragmentsOfDocuments,
DefinitionDecoration,
} from '../namespace';

type schema = addIntrospectionScalars<simpleSchema>;
Expand Down Expand Up @@ -235,8 +235,8 @@ test('infers fragment spreads for masked fragment refs', () => {
query { ...Fields }
`>;

type extraFragments = getFragmentsOfDocumentsRec<
[makeDefinitionDecoration<decorateFragmentDef<fragment>>]
type extraFragments = getFragmentsOfDocuments<
[DefinitionDecoration<decorateFragmentDef<fragment>>]
>;

type actual = getDocumentType<query, schema, extraFragments>;
Expand Down
65 changes: 32 additions & 33 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import type {
} from './introspection';

import type {
getFragmentsOfDocumentsRec,
makeDefinitionDecoration,
DefinitionDecoration,
FragmentShape,
getFragmentsOfDocuments,
decorateFragmentDef,
omitFragmentRefsRec,
makeFragmentRef,
Expand Down Expand Up @@ -123,13 +124,13 @@ interface GraphQLTadaAPI<Schema extends SchemaLike, Config extends AbstractConfi
*
* @see {@link readFragment} for how to read from fragment masks.
*/
<const In extends string, const Fragments extends readonly [...makeDefinitionDecoration[]]>(
<const In extends string, const Fragments extends readonly FragmentShape[]>(
input: In,
fragments?: Fragments
): getDocumentNode<
parseDocument<In>,
Schema,
getFragmentsOfDocumentsRec<Fragments>,
getFragmentsOfDocuments<Fragments>,
Config['isMaskingDisabled']
>;

Expand Down Expand Up @@ -328,7 +329,7 @@ interface TadaDocumentNode<
Decoration = void,
> extends DocumentNode,
DocumentDecoration<Result, Variables>,
makeDefinitionDecoration<Decoration> {}
DefinitionDecoration<Decoration> {}

/** A GraphQL persisted document with attached types for results and variables.
*
Expand Down Expand Up @@ -398,11 +399,9 @@ type VariablesOf<Document> = Document extends DocumentDecoration<any, infer Vari
*
* @see {@link readFragment} for how to read from fragment masks.
*/
type FragmentOf<Document extends makeDefinitionDecoration> = makeFragmentRef<Document>;
type FragmentOf<Document extends FragmentShape> = makeFragmentRef<Document>;

type resultOrFragmentOf<Document extends makeDefinitionDecoration> =
| FragmentOf<Document>
| ResultOf<Document>;
type resultOrFragmentOf<Document extends FragmentShape> = FragmentOf<Document> | ResultOf<Document>;

type resultOfFragmentsRec<
Fragments extends readonly any[],
Expand Down Expand Up @@ -466,59 +465,59 @@ type fragmentRefsOfFragmentsRec<
*
* @see {@link readFragment} for how to read from fragment masks.
*/
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
fragment: resultOrFragmentOf<Document>
): ResultOf<Document>;
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
fragment: resultOrFragmentOf<Document> | null
): ResultOf<Document> | null;
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
fragment: resultOrFragmentOf<Document> | undefined
): ResultOf<Document> | undefined;
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
fragment: resultOrFragmentOf<Document> | null | undefined
): ResultOf<Document> | null | undefined;
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
fragment: readonly resultOrFragmentOf<Document>[]
): readonly ResultOf<Document>[];
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
fragment: readonly (resultOrFragmentOf<Document> | null)[]
): readonly (ResultOf<Document> | null)[];
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
fragment: readonly (resultOrFragmentOf<Document> | undefined)[]
): readonly (ResultOf<Document> | undefined)[];
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
fragment: readonly (resultOrFragmentOf<Document> | null | undefined)[]
): readonly (ResultOf<Document> | null | undefined)[];
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
_document: Document,
fragment: resultOrFragmentOf<Document>
): ResultOf<Document>;
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
_document: Document,
fragment: resultOrFragmentOf<Document> | null
): ResultOf<Document> | null;
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
_document: Document,
fragment: resultOrFragmentOf<Document> | undefined
): ResultOf<Document> | undefined;
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
_document: Document,
fragment: resultOrFragmentOf<Document> | null | undefined
): ResultOf<Document> | null | undefined;
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
_document: Document,
fragment: readonly resultOrFragmentOf<Document>[]
): readonly ResultOf<Document>[];
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
_document: Document,
fragment: readonly (resultOrFragmentOf<Document> | null)[]
): readonly (ResultOf<Document> | null)[];
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
_document: Document,
fragment: readonly (resultOrFragmentOf<Document> | undefined)[]
): readonly (ResultOf<Document> | undefined)[];
function readFragment<const Document extends makeDefinitionDecoration>(
function readFragment<const Document extends FragmentShape>(
_document: Document,
fragment: readonly (resultOrFragmentOf<Document> | null | undefined)[]
): readonly (ResultOf<Document> | null | undefined)[];
Expand Down Expand Up @@ -555,35 +554,35 @@ function readFragment(...args: [unknown] | [unknown, unknown]) {
*
* @see {@link readFragment} for how to read from fragment masks (i.e. the reverse)
*/
function maskFragments<const Fragments extends readonly [...makeDefinitionDecoration[]]>(
function maskFragments<const Fragments extends readonly FragmentShape[]>(
_fragments: Fragments,
fragment: resultOfFragmentsRec<Fragments>
): fragmentRefsOfFragmentsRec<Fragments>;
function maskFragments<const Fragments extends readonly [...makeDefinitionDecoration[]]>(
function maskFragments<const Fragments extends readonly FragmentShape[]>(
_fragments: Fragments,
fragment: resultOfFragmentsRec<Fragments> | null
): fragmentRefsOfFragmentsRec<Fragments> | null;
function maskFragments<const Fragments extends readonly [...makeDefinitionDecoration[]]>(
function maskFragments<const Fragments extends readonly FragmentShape[]>(
_fragments: Fragments,
fragment: resultOfFragmentsRec<Fragments> | undefined
): fragmentRefsOfFragmentsRec<Fragments> | undefined;
function maskFragments<const Fragments extends readonly [...makeDefinitionDecoration[]]>(
function maskFragments<const Fragments extends readonly FragmentShape[]>(
_fragments: Fragments,
fragment: resultOfFragmentsRec<Fragments> | null | undefined
): fragmentRefsOfFragmentsRec<Fragments> | null | undefined;
function maskFragments<const Fragments extends readonly [...makeDefinitionDecoration[]]>(
function maskFragments<const Fragments extends readonly FragmentShape[]>(
_fragments: Fragments,
fragment: readonly resultOfFragmentsRec<Fragments>[]
): readonly fragmentRefsOfFragmentsRec<Fragments>[];
function maskFragments<const Fragments extends readonly [...makeDefinitionDecoration[]]>(
function maskFragments<const Fragments extends readonly FragmentShape[]>(
_fragments: Fragments,
fragment: readonly (resultOfFragmentsRec<Fragments> | null)[]
): readonly (fragmentRefsOfFragmentsRec<Fragments> | null)[];
function maskFragments<const Fragments extends readonly [...makeDefinitionDecoration[]]>(
function maskFragments<const Fragments extends readonly FragmentShape[]>(
_fragments: Fragments,
fragment: readonly (resultOfFragmentsRec<Fragments> | undefined)[]
): readonly (fragmentRefsOfFragmentsRec<Fragments> | undefined)[];
function maskFragments<const Fragments extends readonly [...makeDefinitionDecoration[]]>(
function maskFragments<const Fragments extends readonly FragmentShape[]>(
_fragments: Fragments,
fragment: readonly (resultOfFragmentsRec<Fragments> | null | undefined)[]
): readonly (fragmentRefsOfFragmentsRec<Fragments> | null | undefined)[];
Expand Down
Loading
Loading