Skip to content

Commit

Permalink
Support global fragments (#109)
Browse files Browse the repository at this point in the history
* try and facilitate global fragments

* omg it works

* autocomplete

* finish this up

* cleanup

* do changes

* add tests

* yeet
  • Loading branch information
JoviDeCroock authored Nov 24, 2023
1 parent 78889ec commit 3f434d3
Show file tree
Hide file tree
Showing 33 changed files with 3,998 additions and 164 deletions.
5 changes: 5 additions & 0 deletions .changeset/perfect-news-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@0no-co/graphqlsp': minor
---

Support the GraphQL Code Generator client preset
3 changes: 3 additions & 0 deletions packages/example-external-generator/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
14 changes: 14 additions & 0 deletions packages/example-external-generator/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
schema: './schema.graphql',
documents: ['src/**/*.tsx'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client',
},
},
};

export default config;
24 changes: 24 additions & 0 deletions packages/example-external-generator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "example",
"private": true,
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@graphql-typed-document-node/core": "^3.2.0",
"@urql/core": "^3.0.0",
"graphql": "^16.8.1"
},
"devDependencies": {
"@0no-co/graphqlsp": "file:../graphqlsp",
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/client-preset": "^4.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
}
}
94 changes: 94 additions & 0 deletions packages/example-external-generator/schema.graphql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions packages/example-external-generator/src/Pokemon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
import { graphql } from './gql';

export const PokemonFields = graphql(`
fragment pokemonFields on Pokemon {
id
name
attacks {
fast {
damage
name
}
}
}
`)

export const WeakFields = graphql(`
fragment weaknessFields on Pokemon {
weaknesses
}
`)

export const Pokemon = (data: any) => {
const pokemon = useFragment(PokemonFields, data);
return `hi ${pokemon.name}`;
};

type X = { hello: string };

const x: X = { hello: '' };

export function useFragment<Type>(
_fragment: TypedDocumentNode<Type>,
data: any
): Type {
return data;
}
85 changes: 85 additions & 0 deletions packages/example-external-generator/src/gql/fragment-masking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
ResultOf,
DocumentTypeDecoration,
TypedDocumentNode,
} from '@graphql-typed-document-node/core';
import { FragmentDefinitionNode } from 'graphql';
import { Incremental } from './graphql';

export type FragmentType<
TDocumentType extends DocumentTypeDecoration<any, any>
> = TDocumentType extends DocumentTypeDecoration<infer TType, any>
? [TType] extends [{ ' $fragmentName'?: infer TKey }]
? TKey extends string
? { ' $fragmentRefs'?: { [key in TKey]: TType } }
: never
: never
: never;

// return non-nullable if `fragmentType` is non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>
): TType;
// return nullable if `fragmentType` is nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType:
| FragmentType<DocumentTypeDecoration<TType, any>>
| null
| undefined
): TType | null | undefined;
// return array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
): ReadonlyArray<TType>;
// return array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType:
| ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
| null
| undefined
): ReadonlyArray<TType> | null | undefined;
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType:
| FragmentType<DocumentTypeDecoration<TType, any>>
| ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
| null
| undefined
): TType | ReadonlyArray<TType> | null | undefined {
return fragmentType as any;
}

export function makeFragmentData<
F extends DocumentTypeDecoration<any, any>,
FT extends ResultOf<F>
>(data: FT, _fragment: F): FragmentType<F> {
return data as FragmentType<F>;
}
export function isFragmentReady<TQuery, TFrag>(
queryNode: DocumentTypeDecoration<TQuery, any>,
fragmentNode: TypedDocumentNode<TFrag>,
data:
| FragmentType<TypedDocumentNode<Incremental<TFrag>, any>>
| null
| undefined
): data is FragmentType<typeof fragmentNode> {
const deferredFields = (
queryNode as {
__meta__?: { deferredFields: Record<string, (keyof TFrag)[]> };
}
).__meta__?.deferredFields;

if (!deferredFields) return true;

const fragDef = fragmentNode.definitions[0] as
| FragmentDefinitionNode
| undefined;
const fragName = fragDef?.name?.value;

const fields = (fragName && deferredFields[fragName]) || [];
return fields.length > 0 && fields.every(field => data && field in data);
}
78 changes: 78 additions & 0 deletions packages/example-external-generator/src/gql/gql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint-disable */
import * as types from './graphql';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';

/**
* Map of all GraphQL operations in the project.
*
* This map has several performance disadvantages:
* 1. It is not tree-shakeable, so it will include all operations in the project.
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
* 3. It does not support dead code elimination, so it will add unused operations.
*
* Therefore it is highly recommended to use the babel or swc plugin for production.
*/
const documents = {
'\n fragment pokemonFields on Pokemon {\n id\n name\n attacks {\n fast {\n damage\n name\n }\n }\n }\n':
types.PokemonFieldsFragmentDoc,
'\n fragment weaknessFields on Pokemon {\n weaknesses\n }\n':
types.WeaknessFieldsFragmentDoc,
'\n query Pok($limit: Int!) {\n pokemons(limit: $limit) {\n id\n name\n fleeRate\n classification\n ...pokemonFields\n ...weaknessFields\n __typename\n }\n }\n':
types.PokDocument,
'\n query Po($id: ID!) {\n pokemon(id: $id) {\n id\n fleeRate\n __typename\n }\n }\n':
types.PoDocument,
'\n query PokemonsAreAwesome {\n pokemons {\n id\n }\n }\n':
types.PokemonsAreAwesomeDocument,
};

/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*
*
* @example
* ```ts
* const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
* ```
*
* The query argument is unknown!
* Please regenerate the types.
*/
export function graphql(source: string): unknown;

/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: '\n fragment pokemonFields on Pokemon {\n id\n name\n attacks {\n fast {\n damage\n name\n }\n }\n }\n'
): (typeof documents)['\n fragment pokemonFields on Pokemon {\n id\n name\n attacks {\n fast {\n damage\n name\n }\n }\n }\n'];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: '\n fragment weaknessFields on Pokemon {\n weaknesses\n }\n'
): (typeof documents)['\n fragment weaknessFields on Pokemon {\n weaknesses\n }\n'];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: '\n query Pok($limit: Int!) {\n pokemons(limit: $limit) {\n id\n name\n fleeRate\n classification\n ...pokemonFields\n ...weaknessFields\n __typename\n }\n }\n'
): (typeof documents)['\n query Pok($limit: Int!) {\n pokemons(limit: $limit) {\n id\n name\n fleeRate\n classification\n ...pokemonFields\n ...weaknessFields\n __typename\n }\n }\n'];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: '\n query Po($id: ID!) {\n pokemon(id: $id) {\n id\n fleeRate\n __typename\n }\n }\n'
): (typeof documents)['\n query Po($id: ID!) {\n pokemon(id: $id) {\n id\n fleeRate\n __typename\n }\n }\n'];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: '\n query PokemonsAreAwesome {\n pokemons {\n id\n }\n }\n'
): (typeof documents)['\n query PokemonsAreAwesome {\n pokemons {\n id\n }\n }\n'];

export function graphql(source: string) {
return (documents as any)[source] ?? {};
}

export type DocumentType<TDocumentNode extends DocumentNode<any, any>> =
TDocumentNode extends DocumentNode<infer TType, any> ? TType : never;
Loading

0 comments on commit 3f434d3

Please sign in to comment.