Skip to content

Latest commit

 

History

History
267 lines (207 loc) · 7.42 KB

CUSTOMIZE_TYPE_GEN.md

File metadata and controls

267 lines (207 loc) · 7.42 KB

Customize Type Generator

What can Type Generator Addon do ?

With Type Generator Addons you can:

  • Mapping custom scalar types defined in your GraphQL schema to TypeScript type what you want.
  • Generating new complex types using query result types or fragment types.
  • Adding comment to generated .ts files, like /* tslint:disable */.
  • etc,,,

How to implement Type Generator Addon

Write TypeGenAddonFactory function

First, create a new TypeScript file, my-addon.ts, and edit it as the following:

/* my-addon.ts */

import type { TypeGenAddonFactory } from 'ts-graphql-plugin';

const addonFactory: TypeGenAddonFactory = ctx => {
  return {
    document() {
      ctx.source.writeLeadingComment('Hello, my addon!');
    },
  };
};

module.exports = addonFactory;

Next, edit your tsconfig.json and configure to use the above addon:

/* tsconfig.json */
{
  "compilerOptions": {
    "plugins": [
      {
        "name": "ts-graphql-plugin",
        "schema": "schema.graphql",
        "tag": "gql",
        "typegen": {
          "addons": [
            "./my-addon"
          ]
        }
      }
    ]
  }
}

Run ts-graphql-plugin typegen command. Then, the head of each generated .ts file should be the following:

/* Hello, my addon! */
/* eslint-disable */
/* This is an autogenerated file. Do not edit this file directly! */

// Your query result / variables / fragments types

Template String and Addon Factory

The core type generator generates type files for each GraphQL template string in your TypeScript source files.

For example, the following query template string generates a type file, __generated__/repository-query.ts .

/* ghe-query.ts */

const query = gql`
  # fragment definition
  fragment IssueItem on Issue {
    id
    url
  }

  # fragment definition
  fragment RepoItem on Repository {
    id
    description
    issues(first: 10) {
      nodes {
        ...IssueItem
      }
    }
  }

  # operation definition
  query RepositoryQuery($limit: Int!) {
    viewer {
      repositories(first: $limit) {
        nodes {
          ...RepoItem
        }
      }
    }
  }
`;

And TypeGenAddonFactory function is called once for each GraphQL template string (i.e. for each output type file). So, the ctx argument of this function contains both the input template string and type file content to be output.

ctx has also reference to your GraphQL schema.

GraphQL AST hooks

Addon can implement methods to be called back for top-level GraphQL AST node, FragmentDefinition and OperationDefinition. You can access the type declaration statements the core generator created from the fragments or operations in these callback methods. They're useful to create some complex types from query result types or fragment object types.

const addonFactory: TypeGenAddonFactory = ctx => {
  return {
    fragmentDefinition({ graphqlNode, tsNode }) {
      // graphqlNode is the fragment definition node
      // tsNode is TypeScript type declaration node for the fragment
    },
    operationDefinition({ graphqlNode, tsResultNode, tsVariableNode }) {
      // graphqlNode is the operation(query/mutation/subscription) definition AST node.
      // tsResultNode is TypeScript type declaration node for result of the operation.
      // tsVariableNode is TypeScript type declaration node for variables of the operation.
    },
  };
};

And document callback is corresponding to the whole GraphQL document AST node. It's typically used to implement post-processing for the output .ts file.

const addonFactory: TypeGenAddonFactory = ctx => {
  return {
    document({ graphqlNode }) {
      // graphqlNode is the whole GraphQL document AST node.
    },
  };
};

Custom Scalar hook

customScalar is a special callback method to customize mapping GraphQL scalar field to TypeScript types.

The core generator outputs any for custom scalr fields because the generator does not know which TypeScript type should they be mapped to.

const addonFactory: TypeGenAddonFactory = ctx => {
  return {
    customScalar({ scalarType }) {
      if (scalarType.name === 'URL') {
        return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
      }
    },
  };
};

The above example maps URL custom scalar field to TypeScript string type.

If customScalar function returns undefined, the core generator determines mapping result.

Change output files' content using ctx.source

source in the addon factory context is an utility object to help to change output type file's content.

Add TypeScript statement

You can add TypeScript statements to the output file with pushStatement function. This function accepts ts.Statement AST node object.

const addonFactory: TypeGenAddonFactory = ctx => {
  return {
    document() {
      const statement = ts.factory.createTypeAliasDeclaration(
        undefined,
        [ts.createModifier(ts.SyntaxKind.ExportKeyword)],
        ts.factory.createIdentifier('Hoge'),
        undefined,
        ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
      );
      ctx.source.pushStatement(statement);
    },
  };
};

Tips: Using template function provided from Talt, the above code is equivalent to the following:

import { template } from 'talt';

const addonFactory: TypeGenAddonFactory = ctx => {
  return {
    document() {
      ctx.source.pushStatement(template.statement`export type Hoge = string`());
    },
  };
};

Add import statement

Sometimes, you want to import TypeScript types defined in other files and use them with generated types.

import { AwesomeType } from '../../util/types';

export type RepositoryQuery = {
  viewer: {
    /* ... */
  };
};

export type WrapRepositoryQuery = AwesomeType<RepositoryQuery>;

ctx.source.pushNamedImportIfNeeded helps to create import declaration:

/* my-addon.ts */

import { template } from 'talt';

const addonFactory: TypeGenAddonFactory = ctx => {
  return {
    operationDefinition({ tsResultNode }) {
      ctx.source.pushNamedImportIfNeeded('AwesomeType', '../../util/types');

      ctx.source.pushStatement(
        template.statement`
          export type WRAPPED_TYPE_ID = AwesomeType<RESULT_TYPE_ID>
        `({
          WRAPPED_TYPE_ID: ts.factory.createIdentifier(`Wrap${tsResultNode.name.text}`),
          RESULT_TYPE_ID: ts.factory.createIdentifier(tsResultNode.name.text),
        }),
      );
    },
  };
};

Addon example

There is working example of Type Generator Addon under the project-fixtures/typegen-addon-prj directory in this repository.

API reference

See ts-graphql-plugin's .d.ts files or definition in this repository.