Skip to content

Releases: 0no-co/gql.tada

gql.tada@1.0.0

16 Jan 12:05
1834b34
Compare
Choose a tag to compare

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

11 Jan 20:26
v1.0.0-beta.1
bf6d589
Compare
Choose a tag to compare
v1.0.0-beta.1 Pre-release
Pre-release
  • 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

11 Jan 16:47
v1.0.0-beta.0
372ff76
Compare
Choose a tag to compare
v1.0.0-beta.0 Pre-release
Pre-release

These notes were pre-release instructions on how to use 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 TypedDocumentNodes 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.