Currently, given a query like this—
query findUser($userId: ID!) {
user(id: $userId) {
id
username
role
}
}
—the generator creates this output (assuming typescript-operations and near-operation-file as well for convenience):
import * as Types from './types';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type FindUserQueryVariables = Types.Exact<{
userId: Types.Scalars['ID'];
}>;
export type FindUserQuery = { __typename?: 'Query', user?: Types.Maybe<{ __typename?: 'User', id: string, username: string, role: Types.Role }> };
export const FindUserDocument: DocumentNode<FindUserQuery, FindUserQueryVariables> = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"findUser"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}},"directives":[]}],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"username"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"role"},"arguments":[],"directives":[]}]}}]}}]};
For many purposes, however, there's zero value in the actual TS output, only a type definition, and we in fact want to generate only a .d.ts file, which should look something like this:
import * as Types from './types';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type FindUserQueryVariables = Types.Exact<{
userId: Types.Scalars['ID'];
}>;
export type FindUserQuery = { __typename?: 'Query', user?: Types.Maybe<{ __typename?: 'User', id: string, username: string, role: Types.Role }> };
export const FindUserDocument: DocumentNode<FindUserQuery, FindUserQueryVariables>;
Note: I'm presently working around this with the slightly ridiculous hack of using a regex to remove everything after the DocumentNode<...> declaration. This is obviously fragile and not a good long-term solution for us!
The problem here appears to be upstream: ClientSideBaseVisitor#OperationDefinition bails if the node is not named, so we never even hit the point where the TypeScriptDocumentNodesVisitor's implementation can run. Seems reasonable to move this issue upstream?
I'll open a fix this, and I'm poking through the implementation of ClientSideBaseVisitor to get a handle on how now—but it's not actually clear to me what constraints need to hold with ClientSideBaseVisitor#OperationDefinition.
Will be fixed by dotansimha/graphql-code-generator#4682.
@dotansimha I'm not sure why, but I filed those last two comments as though they were related to this issue… and they're not. 😆 I think we just need an option for this plugin to solve this!
Wanted to second this! I'm using an approach along these lines, letting graphql-tag/loader or similar handle the actual concrete JS generation.
Using this plugin:
https://github.com/dleavitt/apollo-typed-documents
And a config like this:
overwrite: true
schema: "./src/graphql/schema.graphql"
documents: "./src/**/*.{gql,graphql}"
generates:
src/schema.ts:
plugins:
- "typescript"
- "typescript-operations"
src/operations.d.ts:
plugins:
# - "typescript-graphql-files-modules"
- "@dleavitt/apollo-typed-documents/lib/codegenTypedDocuments"
config:
relativeToCwd: true
prefix: ""
typesModule: "schema"
You get the following. Quite a bit tidier.
# src/operations.d.ts
declare module "apps/account/AccountOperations.gql" {
import { TypedDocumentNode } from "@apollo/client"
import {
UserGetCurrentQuery,
UserGetCurrentQueryVariables,
} from "schema"
export const UserGetCurrent: TypedDocumentNode<
UserGetCurrentQueryVariables,
UserGetCurrentQuery
>
import {
UserAccountGetQuery,
UserAccountGetQueryVariables,
} from "schema"
export const UserAccountGet: TypedDocumentNode<
UserAccountGetQueryVariables,
UserAccountGetQuery
>
import {
UserAccountUpdateMutation,
UserAccountUpdateMutationVariables,
} from "schema"
export const UserAccountUpdate: TypedDocumentNode<
UserAccountUpdateMutationVariables,
UserAccountUpdateMutation
>
}
// NB that the type param ordering for TypedDocumentNode is backwards here
Yeah this could work :) Requires some changes in the codegen plugin, but it could be done.
Btw, can you please share how do you use .d.ts files and those declarations? As far as I know, you can't declare module augmentation for a local file (declare module "./my-file.graphql" wont work since it must be a wildcard or a package name).
All you have to do in this case is generate my-file.graphql.js and my-file.graphql.d.ts—using some other mechanism for the JS file and this project to codegen the .d.ts file.
@dotansimha I think it just works? I've got my .d.ts files specified explicitly in my tsconfig but nothing special beyond that.
Requires some changes in the codegen plugin, but it could be done.
Could also do this approach as a separate plugin. I'm happy to clean up the fork I'm working from if you think people would find it useful.
@chriskrycho I'm moving this to codegen repo, let's track it there :)
Just chiming in to express my interest in this feature. It would enable me to add TypedDocumentNode support to apollo elements
// import { HelloQueryData, HelloQueryVariables } from '#schema'; // old style codegen
import { HelloQueryDocument } from '#schema'; // after this issue is merged
import HelloQuery from './Hello.query.graphql'; // actual `.graphql` file, transformed with a build step or dev-server plugin
// old style codegen, but still supported with some type inference
class OldStyleHelloQueryElement extends ApolloQuery<HelloQueryData, HelloQueryVariables> {
query = HelloQuery;
}
// new style, accepting a TypedDocumentNode
class HelloQueryElement extends ApolloQuery<HelloQueryDocument> {
query = HelloQuery;
}
OR
import { HelloQueryDocument } from '#schema'; // full codegen
// OR
import { HelloQueryDocument } from './Hello.query'; // file codegen
// also supported via `typeof`
class HelloQueryElement extends ApolloQuery<typeof HelloQueryDocument> {
query = HelloQueryDocument;
}