Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(gql-tag-operations): support module augmentation
Browse files Browse the repository at this point in the history
n1ru4l committed Aug 17, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent a0a9206 commit ed35c08
Showing 8 changed files with 364 additions and 24 deletions.
6 changes: 6 additions & 0 deletions .changeset/lucky-rabbits-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-codegen/gql-tag-operations': minor
'@graphql-codegen/gql-tag-operations-preset': minor
---

feat: support module augumentation for extending the types of gql functions from existing packages via the `augmentedModuleName` config option.
6 changes: 6 additions & 0 deletions dev-test/codegen.yml
Original file line number Diff line number Diff line change
@@ -392,3 +392,9 @@ generates:
schema: ./dev-test/gql-tag-operations/schema.graphql
documents: './dev-test/gql-tag-operations/src/**/*.ts'
preset: gql-tag-operations-preset
./dev-test/gql-tag-operations-urql/gql:
schema: ./dev-test/gql-tag-operations-urql/schema.graphql
documents: './dev-test/gql-tag-operations-urql/src/**/*.ts'
preset: gql-tag-operations-preset
presetConfig:
augmentedModuleName: '@urql/core'
183 changes: 183 additions & 0 deletions dev-test/gql-tag-operations-urql/gql/graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/* eslint-disable */
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type Maybe<T> = T | null;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
Date: any;
Url: any;
};

export type Meta = {
__typename?: 'Meta';
count?: Maybe<Scalars['Int']>;
};

export type Mutation = {
__typename?: 'Mutation';
createTweet?: Maybe<Tweet>;
deleteTweet?: Maybe<Tweet>;
markTweetRead?: Maybe<Scalars['Boolean']>;
};

export type MutationCreateTweetArgs = {
body?: Maybe<Scalars['String']>;
};

export type MutationDeleteTweetArgs = {
id: Scalars['ID'];
};

export type MutationMarkTweetReadArgs = {
id: Scalars['ID'];
};

export type Notification = {
__typename?: 'Notification';
id?: Maybe<Scalars['ID']>;
date?: Maybe<Scalars['Date']>;
type?: Maybe<Scalars['String']>;
};

export type Query = {
__typename?: 'Query';
Tweet?: Maybe<Tweet>;
Tweets?: Maybe<Array<Maybe<Tweet>>>;
TweetsMeta?: Maybe<Meta>;
User?: Maybe<User>;
Notifications?: Maybe<Array<Maybe<Notification>>>;
NotificationsMeta?: Maybe<Meta>;
};

export type QueryTweetArgs = {
id: Scalars['ID'];
};

export type QueryTweetsArgs = {
limit?: Maybe<Scalars['Int']>;
skip?: Maybe<Scalars['Int']>;
sort_field?: Maybe<Scalars['String']>;
sort_order?: Maybe<Scalars['String']>;
};

export type QueryUserArgs = {
id: Scalars['ID'];
};

export type QueryNotificationsArgs = {
limit?: Maybe<Scalars['Int']>;
};

export type Stat = {
__typename?: 'Stat';
views?: Maybe<Scalars['Int']>;
likes?: Maybe<Scalars['Int']>;
retweets?: Maybe<Scalars['Int']>;
responses?: Maybe<Scalars['Int']>;
};

export type Tweet = {
__typename?: 'Tweet';
id: Scalars['ID'];
body?: Maybe<Scalars['String']>;
date?: Maybe<Scalars['Date']>;
Author?: Maybe<User>;
Stats?: Maybe<Stat>;
};

export type User = {
__typename?: 'User';
id: Scalars['ID'];
username?: Maybe<Scalars['String']>;
first_name?: Maybe<Scalars['String']>;
last_name?: Maybe<Scalars['String']>;
full_name?: Maybe<Scalars['String']>;
/** @deprecated Field no longer supported */
name?: Maybe<Scalars['String']>;
avatar_url?: Maybe<Scalars['Url']>;
};

export type FooQueryVariables = Exact<{ [key: string]: never }>;

export type FooQuery = { __typename?: 'Query'; Tweets?: Maybe<Array<Maybe<{ __typename?: 'Tweet'; id: string }>>> };

export type LelFragment = { __typename?: 'Tweet'; id: string; body?: Maybe<string> };

export type BarQueryVariables = Exact<{ [key: string]: never }>;

export type BarQuery = {
__typename?: 'Query';
Tweets?: Maybe<Array<Maybe<{ __typename?: 'Tweet'; id: string; body?: Maybe<string> }>>>;
};

export const LelFragmentDoc = {
kind: 'Document',
definitions: [
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'Lel' },
typeCondition: { kind: 'NamedType', name: { kind: 'Name', value: 'Tweet' } },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'body' } },
],
},
},
],
} as unknown as DocumentNode<LelFragment, unknown>;
export const FooDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: { kind: 'Name', value: 'Foo' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'Tweets' },
selectionSet: {
kind: 'SelectionSet',
selections: [{ kind: 'Field', name: { kind: 'Name', value: 'id' } }],
},
},
],
},
},
],
} as unknown as DocumentNode<FooQuery, FooQueryVariables>;
export const BarDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: { kind: 'Name', value: 'Bar' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'Tweets' },
selectionSet: {
kind: 'SelectionSet',
selections: [{ kind: 'FragmentSpread', name: { kind: 'Name', value: 'Lel' } }],
},
},
],
},
},
...LelFragmentDoc.definitions,
],
} as unknown as DocumentNode<BarQuery, BarQueryVariables>;
25 changes: 25 additions & 0 deletions dev-test/gql-tag-operations-urql/gql/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-disable */
declare module '@urql/core' {
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';

export function gql(
source: '\n query Foo {\n Tweets {\n id\n }\n }\n'
): typeof import('./graphql').FooDocument;

export function gql(
source: '\n fragment Lel on Tweet {\n id\n body\n }\n'
): typeof import('./graphql').LelFragmentDoc;

export function gql(
source: '\n query Bar {\n Tweets {\n ...Lel\n }\n }\n'
): typeof import('./graphql').BarDocument;

export function gql(source: string): unknown;

export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode<
infer TType,
any
>
? TType
: never;
}
52 changes: 52 additions & 0 deletions dev-test/gql-tag-operations-urql/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
scalar Url
scalar Date

type Tweet {
id: ID!
body: String
date: Date
Author: User
Stats: Stat
}

type User {
id: ID!
username: String
first_name: String
last_name: String
full_name: String
name: String @deprecated
avatar_url: Url
}

type Stat {
views: Int
likes: Int
retweets: Int
responses: Int
}

type Notification {
id: ID
date: Date
type: String
}

type Meta {
count: Int
}

type Query {
Tweet(id: ID!): Tweet
Tweets(limit: Int, skip: Int, sort_field: String, sort_order: String): [Tweet]
TweetsMeta: Meta
User(id: ID!): User
Notifications(limit: Int): [Notification]
NotificationsMeta: Meta
}

type Mutation {
createTweet(body: String): Tweet
deleteTweet(id: ID!): Tweet
markTweetRead(id: ID!): Boolean
}
25 changes: 25 additions & 0 deletions dev-test/gql-tag-operations-urql/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { gql } from 'urql';

const FooQuery = gql(/* GraphQL */ `
query Foo {
Tweets {
id
}
}
`);

const LelFragment = gql(/* GraphQL */ `
fragment Lel on Tweet {
id
body
}
`);

const BarQuery = gql(/* GraphQL */ `
query Bar {
Tweets {
...Lel
}
}
`);
63 changes: 42 additions & 21 deletions packages/plugins/typescript/gql-tag-operations/src/index.ts
Original file line number Diff line number Diff line change
@@ -23,24 +23,43 @@ export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocume

export const plugin: PluginFunction<{
sourcesWithOperations: Array<SourceWithOperations>;
}> = (_, __, { sourcesWithOperations }) => {
if (!sourcesWithOperations) {
augmentedModuleName?: string;
}> = (_, __, config) => {
if (!config.sourcesWithOperations) {
return '';
}

if (config.augmentedModuleName == null) {
return [
`import * as graphql from './graphql';\n`,
`import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';\n`,
`\n`,
...getDocumentRegistryChunk(config.sourcesWithOperations),
`\n`,
...getGqlOverloadChunk(config.sourcesWithOperations, 'lookup'),
`\n`,
`export function gql(source: string): unknown;\n`,
`export function gql(source: string) {\n`,
` return (documents as any)[source] ?? {};\n`,
`}\n`,
documentTypePartial,
].join(``);
}

return [
`import * as graphql from './graphql';\n`,
`import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';\n`,
`\n`,
...getDocumentRegistryChunk(sourcesWithOperations),
`\n`,
...getGqlOverloadChunk(sourcesWithOperations),
`\n`,
`export function gql(source: string): unknown;\n`,
`export function gql(source: string) {\n`,
` return (documents as any)[source] ?? {};\n`,
`}\n`,
documentTypePartial,
].join(``);
`declare module "${config.augmentedModuleName}" {`,
[
`import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';\n`,
`\n`,
...getGqlOverloadChunk(config.sourcesWithOperations, 'augmented'),
`export function gql(source: string): unknown;\n`,
documentTypePartial,
]
.map(line => (line === `\n` ? line : ` ${line}`))
.join(`\n`),

`}`,
].join(`\n`);
};

function getDocumentRegistryChunk(sourcesWithOperations: Array<SourceWithOperations> = []) {
@@ -59,18 +78,20 @@ function getDocumentRegistryChunk(sourcesWithOperations: Array<SourceWithOperati
return lines;
}

function getGqlOverloadChunk(sourcesWithOperations: Array<SourceWithOperations>) {
type Mode = 'lookup' | 'augmented';

function getGqlOverloadChunk(sourcesWithOperations: Array<SourceWithOperations>, mode: Mode) {
const lines: Array<string> = [];

// We intentionally don't use a <T extends keyof typeof documents> generic, because TS
// would print very long `gql` function signatures (duplicating the source).
for (const { operations, ...rest } of sourcesWithOperations) {
const originalString = rest.source.rawSDL!;
lines.push(
`export function gql(source: ${JSON.stringify(originalString)}): (typeof documents)[${JSON.stringify(
originalString
)}];\n`
);
const returnType =
mode === 'lookup'
? `(typeof documents)[${JSON.stringify(originalString)}]`
: `typeof import('./graphql').${operations[0].initialName};`;
lines.push(`export function gql(source: ${JSON.stringify(originalString)}): ${returnType};\n`);
}

return lines;
28 changes: 25 additions & 3 deletions packages/presets/gql-tag-operations/src/index.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,24 @@ import * as typescriptPlugin from '@graphql-codegen/typescript';
import * as gqlTagPlugin from '@graphql-codegen/gql-tag-operations';
import { processSources } from './process-sources';

export type GqlTagConfig = {};
export type GqlTagConfig = {
/**
* @description Instead of generating a `gql` function, this preset can also generate a d.ts that will enhance the `gql` function of your framework.
*
* E.g. `graphql-tag` or `@urql/core`.
*
* @exampleMarkdown
* ```yml
* generates:
* gql/:
* preset: gql-tag-operations-preset
* presetConfig:
* augmentedModuleName: '@urql/core'
*
* ```
*/
augmentedModuleName?: string;
};

export const preset: Types.OutputPreset<GqlTagConfig> = {
buildGeneratesSection: options => {
@@ -36,6 +53,8 @@ export const preset: Types.OutputPreset<GqlTagConfig> = {
{ [`gen-dts`]: { sourcesWithOperations } },
];

const artifactFileExtension = options.presetConfig.augmentedModuleName == null ? `ts` : `d.ts`;

return [
{
filename: `${options.baseOutputDir}/graphql.ts`,
@@ -46,11 +65,14 @@ export const preset: Types.OutputPreset<GqlTagConfig> = {
documents: sources,
},
{
filename: `${options.baseOutputDir}/index.ts`,
filename: `${options.baseOutputDir}/index.${artifactFileExtension}`,
plugins: genDtsPlugins,
pluginMap,
schema: options.schema,
config: options.config,
config: {
...options.config,
augmentedModuleName: options.presetConfig.augmentedModuleName,
},
documents: sources,
},
];

0 comments on commit ed35c08

Please sign in to comment.