npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@evo/apollo-typed-documents

v1.0.0

Published

Get type safety for your apollo documents.

Downloads

95

Readme

apollo-typed-documents

Provides graphql-codegen plugins (https://graphql-code-generator.com/) for type safe GraphQL documents (DocumentNode).

It allows functions to accept a generic TypedDocumentNode<TVariables, TData> so that types of other arguments or the return type can be inferred.

It is helpful for TypeScript projects but also if used only within an IDE, e.g. it works extremely well with VSCode (uses TypeScript behind the scenes).

$ yarn add apollo-typed-documents

codegenTypedDocuments

Generates TypeScript typings for .graphql files.

Similar to @graphql-codegen/typescript-graphql-files-modules (https://graphql-code-generator.com/docs/plugins/typescript-graphql-files-modules).

The difference is that is uses generic types, so that you have type safety with Apollo (e.g. useQuery / useMutation).

The apollo-typed-documents plugin also accepts the same modulePathPrefix, relativeToCwd and prefix config settings as typescript-graphql-files-modules.

Install

$ yarn add @graphql-codegen/add @graphql-codegen/typescript @graphql-codegen/typescript-operations
$ yarn add apollo-typed-documents

codegen.yml:

schema: schema.graphql
documents: src/**/*.graphql
config:
  scalars:
    Date: string
generates:
  ./src/codegenTypedDocuments.d.ts:
    plugins:
      - apollo-typed-documents/lib/codegenTypedDocuments
    config:
      typesModule: "@codegen-types"
  ./src/codegenTypes.d.ts:
    plugins:
      - add:
          placement: prepend
          content: 'declare module "@codegen-types" {'
      - add:
          placement: append
          content: "}"
      - typescript
      - typescript-operations

tsconfig.json:

Add node_modules/apollo-typed-documents/lib/reactHooks.d.ts in include to override the typings for hooks of @apollo/client, so that types can be inferred from typed documents.

{
  "compilerOptions": {
    "noEmit": true,
    "allowJs": true,
    "checkJs": true,
    "strict": true,
    "jsx": "react",
    "esModuleInterop": true
  },
  "include": ["src", "node_modules/apollo-typed-documents/lib/reactHooks.d.ts"]
}

Config

typesModule: string - Import location for types generated by typescript operations plugin.

relativeToCwd?: boolean (false) - By default, only the filename is used to generate TS module declarations. Setting this to `true` will generate it with a full path based on the CWD.

prefix?: string - By default all module paths will be prefixed with "*\/". Override this to remove or specify a custom prefix (helpful to set to "" in conjunction relativeToCwd.)

stripPrefix?: string - Used in conjunction with relativeToCwd if if imports should be relative to some subdirectory within the cwd, e.g. "src".

typedDocumentNodeModule?: string -Import location for TypedDocumentNode. By default "@graphql-typed-document-node/core"

excludeDefaultExports?: boolean - Exclude default exports from modules.

operationResultSuffix?: string - Add suffix to generated operation result type names e.g we have operation name Author and suffix QueryType, we will get AuthorQueryType

useOperationNameAsSuffix?: boolean - use operation name as suffix e.g we have operation query Author, we will get AuthorQuery e.g we have operation mutation Author we will get AuthorMutation

Example

src/authors.graphql:

query authors {
  authors {
    id
    createdAt
    name
    description
    books {
      id
      title
    }
  }
}

src/createAuthor.graphql:

mutation createAuthor($input: AuthorInput!) {
  createAuthor(input: $input) {
    id
    createdAt
    name
    description
    books {
      id
      title
    }
  }
}

src/codegenTypedDocuments.d.ts (generated):

declare module "*/authors.graphql" {
  import { TypedDocumentNode } from "apollo-typed-documents";
  import { AuthorsQuery, AuthorsQueryVariables } from "@codegen-types";
  export const authors: TypedDocumentNode<AuthorsQueryVariables, AuthorsQuery>;
  export default authors;
}

declare module "*/createAuthor.graphql" {
  import { TypedDocumentNode } from "apollo-typed-documents";
  import { CreateAuthorMutation, CreateAuthorMutationVariables } from "@codegen-types";
  export const createAuthor: TypedDocumentNode<CreateAuthorMutationVariables, CreateAuthorMutation>;
  export default createAuthor;
}

src/AuthorList.js:

import { useMutation, useQuery } from "@apollo/client";
import React from "react";

import authorsQuery from "./authors.graphql";
import createAuthorMutation from "./createAuthor.graphql";

const AuthorList = () => {
  // Type of `data` is inferred (AuthorsQuery)
  const { data } = useQuery(authorsQuery);
  const [createAuthor] = useMutation(createAuthorMutation);

  return (
    <>
      <ul>
        {data?.authors.map((author) => (
          <li key={author.id}>{author.name}</li>
        ))}
      </ul>
      <button
        onClick={() => {
          createAuthor({
            // Type of variables is inferred (CreateAuthorMutationVariables)
            variables: { input: { name: "Foo", books: [{ title: "Bar" }] } },
          });
        }}
      >
        Add
      </button>
    </>
  );
};

export default AuthorList;

Notes for create-react-app users

create-react-app supports graphql.macro for loading .graphql files (https://create-react-app.dev/docs/loading-graphql-files/).

The codegenTypedDocuments plugin generates ambient module declarations for .graphql files, which means that .graphql files must be imported as regular modules (import syntax) so that TypeScript knows about the types.

You can use the babel plugin babel-plugin-import-graphql (https://github.com/detrohutt/babel-plugin-import-graphql), but then you need to use react-app-rewired (https://github.com/timarney/react-app-rewired/) and customize-cra (https://github.com/arackaf/customize-cra) so that you can define custom babel plugins.

$ yarn add react-app-rewired customize-cra
$ yarn add babel-plugin-import-graphql

config-overrides.js

const { override, useBabelRc } = require("customize-cra");

module.exports = override(useBabelRc());

.babelrc

{
  "presets": ["react-app"],
  "plugins": ["babel-plugin-import-graphql"]
}

package.json

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  ...
}

If you have a TypeScript app, you need to override the @apollo/client types for hooks in tsconfig.json:

tsconfig.json

{
  "compilerOptions": {
    ...
  },
  "include": ["src", "node_modules/apollo-typed-documents/lib/reactHooks.d.ts"]
}

If you don't have a TypeScript app (you just want TypeScript support within your IDE) you can't create a tsconfig.json in your app folder, because create-react-app uses that file to detect if this is a TypeScript project.

Instead, you have to create the tsconfig.json in your src folder:

src/tsconfig.json

{
  "compilerOptions": {
    "noEmit": true,
    "allowJs": true,
    "checkJs": true,
    "strict": true,
    "jsx": "react",
    "esModuleInterop": true
  },
  "include": [".", "../node_modules/apollo-typed-documents/lib/reactHooks.d.ts"]
}

Please see the following example projects for more details:

codegenApolloMock

Creates a helper method to easily create mocks for Apollo MockedProvider (https://www.apollographql.com/docs/react/api/react-testing/#mockedprovider).

The returned object is guaranteed to conform to the GraphQL Schema of the query / mutation: reference.

For required (non-null) fields which are not provided (in data / variables), it will use a default value (e.g. "Author-id").

For optional fields which are not provided (in data / variables), it will use undefined for variables and null for data.

Works for any nested selections (data) and any nested inputs (variables).

It will include __typename in data by default. This can be deactivated as an option:

apolloMock(documentNode, variables, result, { addTypename: false });

When used together with codegenTypedDocuments the data and variables are type checked (type inference).

To mock errors, you can provide errors in result (GraphQLError) or pass an Error instead of result:

apolloMock(documentNode, variables, { errors: [new GraphQLError("Already exists")] });
apolloMock(documentNode, variables, new Error("Network error"));

Install

$ yarn add apollo-typed-documents

codegen.yml

schema: schema.graphql
documents: src/**/*.graphql
config:
  scalars:
    Date: string
generates:
  ./src/apolloMock.js:
    plugins:
      - apollo-typed-documents/lib/codegenApolloMock

Example

schema.graphql:

scalar Date

type Author {
  id: ID!
  createdAt: Date!
  name: String!
  description: String
  books: [Book!]!
}

type Book {
  id: ID!
  title: String!
}

input AuthorInput {
  name: String!
  description: String
  books: [BookInput!]!
}

input BookInput {
  title: String!
}

type Query {
  authors: [Author!]!
}

type Mutation {
  createAuthor(input: AuthorInput!): Author!
}

schema {
  query: Query
  mutation: Mutation
}

src/authors.graphql:

query authors {
  authors {
    id
    createdAt
    name
    description
    books {
      id
      title
    }
  }
}

src/createAuthor.graphql:

mutation createAuthor($input: AuthorInput!) {
  createAuthor(input: $input) {
    id
    createdAt
    name
    description
    books {
      id
      title
    }
  }
}

src/apolloMock.js (generated):

See: reference

src/apolloMock.test.js:

import { GraphQLError } from "graphql";

import apolloMock from "./apolloMock";
import authors from "./authors.graphql";
import createAuthor from "./createAuthor.graphql";

describe("apolloMock", () => {
  it("produces the minimal output that is valid according to graphql schema", () => {
    expect(apolloMock(authors, {}, {})).toEqual({
      request: {
        query: authors,
        variables: {},
      },
      result: {
        data: {
          authors: [],
        },
      },
    });

    expect(apolloMock(authors, {}, { data: { authors: [{}] } })).toEqual({
      request: {
        query: authors,
        variables: {},
      },
      result: {
        data: {
          authors: [
            {
              __typename: "Author",
              id: "Author-id",
              createdAt: "Author-createdAt",
              name: "Author-name",
              description: null,
              books: [],
            },
          ],
        },
      },
    });

    expect(
      apolloMock(
        createAuthor,
        { input: { name: "Foo", books: [{ title: "Bar" }] } },
        { data: { createAuthor: { name: "Foo", books: [{ title: "Bar" }] } } }
      )
    ).toEqual({
      request: {
        query: createAuthor,
        variables: {
          input: {
            name: "Foo",
            description: undefined,
            books: [{ title: "Bar" }],
          },
        },
      },
      result: {
        data: {
          createAuthor: {
            __typename: "Author",
            id: "Author-id",
            createdAt: "Author-createdAt",
            name: "Foo",
            description: null,
            books: [
              {
                __typename: "Book",
                id: "Book-id",
                title: "Bar",
              },
            ],
          },
        },
      },
    });
  });

  it("allows overriding default values for scalar types", () => {
    const scalarValues = { Date: "2020-01-01" };

    expect(
      apolloMock(authors, {}, { data: { authors: [{}] } }, { scalarValues })
    ).toEqual({
      request: {
        query: authors,
        variables: {},
      },
      result: {
        data: {
          authors: [
            {
              __typename: "Author",
              id: "Author-id",
              createdAt: "2020-01-01",
              name: "Author-name",
              description: null,
              books: [],
            },
          ],
        },
      },
    });
  });

  it("supports errors", () => {
    expect(
      apolloMock(authors, {}, { errors: [new GraphQLError("Already exists")] })
    ).toEqual({
      request: {
        query: authors,
        variables: {},
      },
      result: {
        errors: [new GraphQLError("Already exists")],
      },
    });

    expect(apolloMock(authors, {}, new Error("Network error"))).toEqual({
      request: {
        query: authors,
        variables: {},
      },
      error: new Error("Network error"),
    });
  });
});