-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Currently there are a number of specialized plugins and plugin configurations to produce code for different client-side environments. This is a proposal to generalize generated code so that one form of output can serve the needs of any client environment.
For example the typescript/react-apollo plugin can emit components or hooks (or HOCs):
// React Component
export const GetPostsComponent = (
props: Omit<
Omit<
ReactApollo.QueryProps<GetPostsQuery, GetPostsQueryVariables>,
"query"
>,
"variables"
> & { variables?: GetPostsQueryVariables }
) => (
<ReactApollo.Query<GetPostsQuery, GetPostsQueryVariables>
query={GetPostsDocument}
{...props}
/>
)
// React hook
export function useGetPostsQuery(
baseOptions?: ReactApolloHooks.QueryHookOptions<GetPostsQueryVariables>
) {
return ReactApolloHooks.useQuery<GetPostsQuery, GetPostsQueryVariables>(
GetPostsDocument,
baseOptions
)
}
It is possible to generalize to reduce the surface area of code generation. Instead of emitting functions with type parameters filled in we could emit GraphQL document nodes that carry type parameters with them like this:
interface TypedDocumentNode<_TData, _TVariables> extends DocumentNode {}
export const getPostsQuery: TypedDocumentNode<GetPostsQuery, GetPostsQueryVariables> = GetPostsDocument
This would require libraries that consume a DocumentNode
to change to accept a compatible type that carries parameters (the TypedDocumentNode
type in the example above.) For example the signature for react-apollo-hooks' useQuery
function would change to look like this:
export function useQuery<
TData = any,
TVariables = OperationVariables,
TCache = object
>(
query: TypedDocumentNode<TData, TVariables>,
options?: QueryHookOptions<TVariables, TCache>
): QueryHookResult<TData, TVariables>
And here is an example of usage:
import { useQuery } from "react-apollo-hooks"
import { getPostsQuery } from "./generated/graphql.ts"
export function App(props: { postId: string }) {
const { data, error, loading } = useQuery(getPostsQuery, {
variables: { postId },
})
/* ... */
}
The type of getPostsQuery
comes with parameters that flow into useQuery
which allows Typescript to infer the correct types for data
, and for the variables
option to useQuery
.
TypedDocumentNode
is compatible with DocumentNode
so it would still be possible to use this new version of useQuery
with arguments that do not carry type parameters, such as the return value from an inline gql
invocation.
A similar change can be made to react-apollo - changing the QueryProps
type to accept a value of type TypedDocumentNode
allows types to flow from a generated operation to the Query
component:
import { Query } from "react-apollo"
import { getPostsQuery } from "./generated/graphql.tsx"
export function App(props: { postId: string }) {
return (
<Query query={getPostsQuery} variables={{ postId: props.postId }}>
{({ data }) => (data ? <div>{data[0].title}</div> : "...")}
</Query>
)
}
Once again, type parameters flow from getPostsQuery
to Query
props which results in accurate type inference for data
, and for the variables
prop.
The result would be to dramatically reduce the burden on graphql-codegen plugins to accommodate different client-side libraries and configurations. I propose adding an operationValues
configuration option to @graphql-codegen/typescript-operations to emit typed document node values.
This plan does come with some external dependencies:
- There must be a type like
TypedDocumentNode
defined in some shared library - ideally in @types/graphql. (OrDocumentNode
itself could be changed to add optional type parameters.) - Client libraries must be updated to use that new type.