Codegen: TypedDocumentNode
TypedDocumentNode is a new technique where the query
, mutation
, or subscription
object for your components comes with it's data
and variables
types built in. Before TypedDocumentNode
, if you wanted to strongly type your GraphQL operations, you would need to define the data
and variables
types manually, or use graphql-codegen
to create those types, which you could manually apply to operations.
query TypedQuery($var: String) {
typed {
id
name
}
}
Generating TypedDocumentNode
s
Using TypedDocumentNode
and graphql-codegen, you can somewhat simplify the process. You can take a variety of approaches here.
Transpile .graphql
to .ts
in-place
Under this approach, you configure graphql-codegen
to generate a single TypeScript files corresponding to each of your GraphQL operations, e.g. src/components/typed/TypedQuery.query.graphql
-> src/components/types/TypedQuery.query.graphql.ts
import { TypedQuery } from './TypedQuery.query.graphql';
- ✅ Single import per operation aids code splitting
- ❌ Requires another file watcher for development
Bundle .graphql
operations into a single operations.ts
file
Under this approach, you configure graphql-codegen
to create a single TypeScript file that exports runtime objects for each operation. e.g. src/components/**/*.{query,mutation,subscription}.graphql
-> src/codegen/operations.ts
import { TypedQuery } from '../../codegen/operations';
- ✅ Keeps code-generated files in one place
- ❌ Monolithic file for operations could be a bundle-size concern
Generate a single operations.d.ts
Under this approach, you configure graphql-codegen
to output a single, types-only TypeScript file. You would then use a build tool and/or dev-server plugin to import the GraphQL files. This approach is most like the old graphql-codegen
workflow that did not use TypedDocumentNode
.
import type { TypedQueryDocument } from '../../codegen/operations';
import TypedQuery from './Typed.query.graphql';
- ✅ Simpler migration from old workflow
- ❌ Requires tooling to load GraphQL files
Using them in Components
When you pass your TypedDocumentNode
to a controller (or haunted hook, or hybrids factory), TypeScript will infer the return type. If you're using a component class (e.g. class extends ApolloQuery
), pass the TypedDocumentNode
objects' type as the first type argument to your component constructors.
These examples assume you followed the first approach outlined above. If you are generating types, omit the typeof
operator.
import type { ApolloQueryElement } from '@apollo-elements/components';
import { TypedQuery } from './Typed.query.graphql';
import '@apollo-elements/components';
type TypedHTMLQueryElement = ApolloQueryElement<typeof TypedQuery>;
const typedHTMLQueryElement =
document.querySelector<TypedHTMLQueryElement>('apollo-query');
typedHTMLQueryElement.query = TypedQuery;
type DataType = (typeof typedHTMLQueryElement)['data'];
import { ApolloQueryMixin } from '@apollo-elements/mixins';
import { TypedQuery } from './Typed.query.graphql';
class TypedQueryElement
extends ApolloQueryMixin(HTMLElement)<typeof TypedQuery> {
query = TypedQuery;
#data: this['data'];
get data(): this['data'] { return this.#data; }
set data(value: this['data']) {
this.#data = value;
if (data !== null)
console.assert(typeof this.#data.name === 'string');
return this.#data;
}
}
import { LitElement, PropertyValues } from 'lit';
import { ApolloQueryController } from '@apollo-elements/core';
import { TypedQuery } from './Typed.query.graphql';
class TypedQueryElement extends LitElement {
query = new ApolloQueryController(this, TypedQuery, {
onData: (data) => {
if (data)
console.assert(typeof this.query.data.name === 'string');
}
});
}
import { FASTElement, customElement, html } from '@microsoft/fast-element';
import { ApolloQueryBehavior } from '@apollo-elements/fast';
import { TypedQuery } from './Typed.query.graphql';
@customElement({ name: 'typed-element' })
class TypedQueryElement extends FASTElement {
query = new ApolloQueryBehavior(this, TypedQuery, {
onData(data) {
if (newValue !== null)
console.assert(typeof newValue.name === 'string');
}
});
}
import { useQuery } from '@apollo-elements/haunted';
import { TypedQuery } from './Typed.query.graphql';
function TypedQueryElement() {
const { data } = useQuery(TypedQuery);
if (data !== null)
console.assert(typeof data.name === 'string');
}
import { useQuery } from '@apollo-elements/atomico';
import { TypedQuery } from './Typed.query.graphql';
function TypedQueryElement() {
const { data } = useQuery(TypedQuery);
if (data !== null)
console.assert(typeof data.name === 'string');
return <host>...</host>;
}
import { define, query, html } from '@apollo-elements/hybrids';
import { TypedQuery } from './Typed.query.graphql';
type ApolloQueryElement<T extends TypedDocumentNode> =
HTMLElement & { query: ApolloQueryController<T> };
define<ApolloQueryElement<typeof TypedQuery>>('typed-query', {
query: query(TypedQuery),
render: ({ query: { data } }) => {
if (data !== null)
console.assert(typeof data.name === 'string');
}
});