This repository contains the code for two GraphQL Codegen plugins that customize the generated code following suggested changes inside ATMINA Solutions GmbH.
All examples in this document are based on the example schema used for testing.
While it's not required to do so, installing and using both plugins is recommended since they have been built together to achieve a certain output. Using either of the plugins standalone might lead to type errors.
Using Yarn:
yarn add -D @atmina/only-enum-types @atmina/local-typescript-operations graphql @graphql-codegen/near-operation-file-preset @graphql-codegen/cli
Using npm:
npm install --save-dev @atmina/only-enum-types @atmina/local-typescript-operations graphql @graphql-codegen/near-operation-file-preset @graphql-codegen/cli
This plugin is to be used like this (part of codegen.yml
):
generates:
src/__generated__/graphql.ts:
plugins:
- "@atmina/only-enums"
The plugin takes care of only generating enum types in the file generated outside of any module.
Your GraphQL server should have a mechanism to deal with the @exported directive. Two options are
- Configuring the directive (recommended to allow copy-pasting of queries to tools like Insomnia)
- An interceptor that removes the directive before handing it off to the GraphQL processor
If you are using the JS GraphQL Plugin for Jetbrains IDEs (and likely other plugins for other editors), you need to include a static graphql file defining the directive in your .graphqlconfig
:
{
"schemaPath": "schema.graphql",
"includes": ["*"], // This is the relevant line; adapt the glob if you have to
"extensions": {
"endpoints": {
"Default Introspection Endpoint": {
"url": "http://localhost:5000",
"introspect": true,
"headers": {
"user-agent": "JS GraphQL"
}
}
}
}
}
# export-directive.graphql
directive @export(exportName: String!) on FIELD
This plugin should be configured like this:
src/:
preset: near-operation-file
presetConfig:
baseTypesPath: __generated__/graphql.ts
extension: .generated.ts
plugins:
- add:
content: "/* tslint:disable */"
- "@atmina/local-typescript-operations"
@atmina/local-typescript-operations
has been developed for, and not tested without the near-operation-file preset.
This plugin achieves the larger part of the concept with features including:
- No use of
Scalars[]
, instead the TS types are used immediately - No use of
Pick<Type, 'field1', 'field2'>
, instead the types are cleanly built as one - No use of
Maybe<Type>
, insteadType | null
is used. - Local generation of the required input types
- Enums are prefixed with
Types.
so they are imported from the file generated by a typescript plugin variant - Fragments are imported between files to make them available everywhere where they are needed (as they have previously all been generated in the same file)
- Non-primitive fields (fields with types other than scalar or enum) may be annotated with a directive (
@export(exportName: "Example")
) to generate a type calledExample
specific to the selection performed on the annotated field.- Attempting to export a primitive field will throw an error during code generation
An example of using the export directive looks like this:
# example.graphql
query GetShelves {
shelves @export(exportName: "Shelf") {
id
floor
}
}
// example.generated.ts
/* tslint:disable */
import * as Types from './__generated__/types';
export type BasicQueryQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type BasicQueryQuery = { __typename?: 'Query'; shelves: Array<Shelf>};
export type Shelf = { __typename?: 'Shelf', id: string, floor: number };
The directive works in fragments and inline fragments as well and generates multiple types for graphql interface types. The naming for those is <exportName>_<typename>
with exportName being the exportName defined in the directive and typeName being the name of the type implementing the parent type the selection is performed on:
# example.graphql
query GetSomeFragen {
shelves {
items @export(exportName: "Borrowable") { # parent (interface) type is Borrowable, implementations are Book and VideoGame
id
}
}
}
// example.generated.ts
/* tslint:disable */
import * as Types from './__generated__/types';
export type GetSomeFragenQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type GetSomeFragenQuery = {
__typename?: 'Query',
shelves: Array<{ __typename?: 'Shelf'; items: Array<Borrowable>}>
};
export type Borrowable = Borrowable_Book | Borrowable_VideoGame;
export type Borrowable_Book = { __typename?: 'Book', id: string };
export type Borrowable_VideoGame = { __typename?: 'VideoGame', id: string };
This also works with additional properties from a specific implementing type that are exported:
# example.graphql
query AllFragen {
shelves {
items @export(exportName: "Borrowable") {
id
title
... on Book {
author @export(exportName: "Author") {
id
name
}
}
... on VideoGame {
publisher
}
}
}
}
// example.generated.ts
/* tslint:disable */
import * as Types from './__generated__/types';
export type AllFragenQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type AllFragenQuery = { __typename?: 'Query',
shelves: Array<{ __typename?: 'Shelf'; items: Array<Borrowable>}>
};
export type Borrowable = Borrowable_Book | Borrowable_VideoGame;
export type Borrowable_Book = { __typename?: 'Book', id: string, title: string, author: Author};
export type Borrowable_VideoGame = { __typename?: 'VideoGame', publisher: string, id: string, title: string };
export type Author = { __typename?: 'Author', id: string, name: string };
To use the plugins in codegen, add them as plugin with their packagename:
schema: "schema.graphql"
documents: "src/**/*.graphql"
hooks:
afterAllFileWrite:
- prettier --write
generates:
src/__generated__/graphql.ts:
plugins:
- "@atmina/only-enum-types"
src/:
preset: near-operation-file
presetConfig:
baseTypesPath: __generated__/graphql.ts
extension: .generated.ts
plugins:
- add:
content: "/* tslint:disable */"
- "@atmina/local-typescript-operations"
If you want to generate additional code like injectable Angular services, install and add plugins to the config as required.