Releases: 0no-co/gql.tada
gql.tada@1.0.0
Initial Release
gql.tada
is a GraphQL document authoring library, inferring the result and variables types
of GraphQL queries and fragments in the TypeScript type system. It derives the types for your
GraphQL queries on the fly allowing you to write type-safe GraphQL documents quickly.
To get started, check out the documentation’s “Get Started” section.
v1.0.0-beta.1
- Fix nullable variables not being optional
- Fix nullable input object fields not being optional
- Flatten fragment refs for readability in type hints
v1.0.0-beta.0
These notes were pre-release instructions on how to use gql.tada
.
For up-to-date instructions, check out the docs.
gql.tada
.For up-to-date instructions, check out the docs.
The type-safe authoring experience of GraphQL documents today on the client-side is complex and requires making several cumbersome choices. It's far behind the code-first experience of GraphQL schema builders.
gql.tada
’s aim is to reduce the mental overhead and friction caused by client-side GraphQL query tools to as close to zero as possible, together with the related GraphQLSP project.
If you’re familiar with graphql-tag
or gql()
functions, gql.tada
is basically a drop-in replacement that adds type-safety.
Check out the current README for a full introduction of the project
This is gql.tada
’s first official release and we’re tagging it straight into a beta.
We’re looking to fix issues and address shortcomings during this phase while moving towards a release candidate later on, before an official v1.0.0
release, hopefully coming soon.
Where are we at?
Currently, gql.tada
is feature-complete, in theory, as far as we’re aware. It supports:
- All type refs, type unwrapping, type mapping, selection inference, variables inference, etc
@defer
,@skip
,@include
switching fields and fragments to be optional (i.e.| undefined
)- inline fragment and local fragment spread inference
- fragment spreads creating fragment masks and fragment unmasking
- mapping over possible types of a given union or interface while inferring selection types
- inferring
__typename
field exact types - customising scalars and setting up an introspection query
We support setting up the schema with your introspection query in two ways.
Option 1: Global Mode
In global mode we declare our introspection query and scalars globally, and can then import graphql
from gql.tada
and use it directly, without any further setup.
import { graphql } from 'gql.tada';
import { myIntrospectionQuery } from './fixtures/introspection';
declare module 'gql.tada' {
interface setupSchema {
introspection: typeof myIntrospectionQuery;
scalars: { DateTime: string };
}
}
const query = graphql(`
{
hello
...HelloWorld
}
`);
Option 2: Init Function
With the initGraphQLTada
function, we instead pass the setup object as a generic and create the graphql
function from scratch.
import { initGraphQLTada } from 'gql.tada';
import { myIntrospectionQuery } from './fixtures/introspection';
export const graphql = initGraphQLTada<{
introspection: typeof myIntrospectionQuery;
scalars: { DateTime: string };
}>();
const query = graphql(`
{
hello
...HelloWorld
}
`);
What’s left to do?
As gql.tada
doesn't aim to provide error output or diagnostics this is better served by GraphQLSP. The LSP/tsserver plugin provides the missing:
- autocompletion features
- GraphQL type info on hover
- validation, diagnostics, and errors
Furthermore, a missing piece currently is generating the introspection query data.
Currently, gql.tada
(apart from the docs not being done) would contain no instructions or guidance on how to introspect your schema and save the introspection query data to a file. As can be seen in the examples above (See: myIntrospectionQuery
), this data is necessary to provide type information to gql.tada
.
We currently have two plans for this:
- Let GraphQLSP generate the introspection data on the fly and keep it up-to-date. This would be automatic and pretty seamless
- Investigate integration into code-first GraphQL API implementations (such as Pothos and gqtx). Code-first GraphQL APIs can in theory derive the introspection data in the TypeScript type system, hence closing the gap between GraphQL API and front-end development entirely without codegen.
In short however, we'd like GraphQLSP to have a preset to recognise all possible patterns of gql.tada
, provide its LSP features to it, and to generate introspection data.
How do I use gql.tada
today?
Today, you will have to create a script to generate the introspection query file yourself.
You may use code such as getIntrospectedSchema
in @urql/introspection
.
The output should look something like src/__tests__/fixtures/simpleIntrospection.ts
, which allows us to get the TypeScript type of the introspection using typeof introspectionQuery
, as long as the format looks something like the following:
export const introspection = {
__schema: {
queryType: { name: 'Query' },
mutationType: { name: 'Mutation' },
subscriptionType: { name: 'Subscription' },
types: [
// ...
],
},
} as const;
After setting gql.tada
up with the introspection data, we may create and interpolate fragments into queries, such as,
import { graphql } from 'gql.tada';
const fragment = graphql(`
fragment HelloWorld extends Query {
hello
world
}
`);
const query = graphql(
`
{
hello
...HelloWorld
}
`,
[fragment]
);
Passing query
in the above example to a GraphQL client supporting TypedDocumentNode
s will automatically infer the result and variables types of the query for you.
For example, in urql
, you may see something like this,
import { useQuery } from 'urql';
import { graphql } from 'gql.tada';
const TestQuery = graphql(`
{ test }
`);
function TestComponent() {
const [result] = useQuery({
query: TestQuery
});
return null;
}
For more information on this in urql
, check the “TypeScript integration” guide to see what this looks like.
When spreading a fragment, like in the example above from a separate document, gql.tada
will create a fragment mask.
This means that when we pass a fragment to graphql
, the result type will only contain a reference to the fragment where we've spread it, and we need to unwrap it.
This pattern promotes the usage of fragments for data requirements and fragment composition in your app.
Learn more about fragment masking in the Guild’s blog post about the topic in GraphQL Code Generator.
For our app code, this means that we have to use FragmentOf<>
to derive the type of fragment masks, and readFragment
to unwrap fragments,
import { FragmentOf, readFragment, graphql } from 'gql.tada';
export const HelloFragment = graphql(`
fragment Hello extends Query {
hello
world
}
`);
const HelloComponent = (props: {
// : { [$tada.fragmentRefs]: {...} }
data: FragmentOf<typeof fragment>
}) => {
// : { hello: unknown, world: unknown }
const hello = readFragment(HelloFragment, data);
};
Apart from FragmentOf
, we also export ResultOf
to get the result type of documents, and VariablesOf
to get the variables of a given operation document directly.
What’s in store for the future?
We aim to make the above as streamlined as possible, and discover missing pieces and APIs as soon as possible.
Afterwards, we've got some ideas for what could come post a v1.0.0
release.
We could for example derive types for @urql/exchange-graphcache
all in the type system.
Or, as mentioned prior, we could integrate with server-side schema builders to not require an introspection file at all.
For now however, the aim is to get the documentation and tsserver/GraphQLSP integration right.